diff --git a/.gitignore b/.gitignore index d3d839f..d17edae 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,5 @@ gradle/* *.project *~ +jsexport/jsexport.js* +battle_srv/room_*.txt diff --git a/README.md b/README.md index da67362..6342f40 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ _(how rollback-and-chase in this project roughly works)_ ## 1.1 Tools to install ### Backend - [Command Line Tools for Xcode](https://developer.apple.com/download/all/?q=command%20line%20tools) (on OSX) or [TDM-GCC](https://jmeubank.github.io/tdm-gcc/download/) (on Windows) (a `make` executable mandatory) -- [Golang1.19.1](https://golang.org/dl/) (mandatory, in China please try a mirror site like [that of ustc](https://mirrors.ustc.edu.cn/golang/)) +- [Golang1.18.6](https://golang.org/dl/) (brought down to 1.18 for GopherJs support, mandatory, in China please try a mirror site like [that of ustc](https://mirrors.ustc.edu.cn/golang/)) - [MySQL 5.7](https://dev.mysql.com/downloads/windows/installer/5.7.html) (mandatory, for OSX not all versions of 5.7 can be found thus 5.7.24 is recommended) - [Redis 3.0.503 or above](https://redis.io/download/) (mandatory) - [skeema](https://www.skeema.io/) (optional, only for convenient MySQL schema provisioning) diff --git a/battle_srv/go.mod b/battle_srv/go.mod index 71dab42..0a19de0 100644 --- a/battle_srv/go.mod +++ b/battle_srv/go.mod @@ -1,6 +1,6 @@ module battle_srv -go 1.19 +go 1.18 require ( github.com/Masterminds/squirrel v0.0.0-20180815162352-8a7e65843414 @@ -16,12 +16,13 @@ require ( github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 - github.com/solarlune/resolv v0.5.1 github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713 go.uber.org/zap v1.9.1 google.golang.org/protobuf v1.28.1 dnmshared v0.0.0 + jsexport v0.0.0 + resolv v0.0.0 ) require ( @@ -48,4 +49,6 @@ require ( replace ( dnmshared => ../dnmshared + jsexport => ../jsexport + resolv => ../resolv_tailored ) diff --git a/battle_srv/models/barrier.go b/battle_srv/models/barrier.go deleted file mode 100644 index ec3b062..0000000 --- a/battle_srv/models/barrier.go +++ /dev/null @@ -1,9 +0,0 @@ -package models - -import ( - . "dnmshared/sharedprotos" -) - -type Barrier struct { - Boundary *Polygon2D -} diff --git a/battle_srv/models/pb_type_convert.go b/battle_srv/models/pb_type_convert.go index ae42687..b24bb48 100644 --- a/battle_srv/models/pb_type_convert.go +++ b/battle_srv/models/pb_type_convert.go @@ -1,17 +1,125 @@ package models import ( - . "battle_srv/protos" + pb "battle_srv/protos" + "jsexport/battle" ) -func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) map[int32]*PlayerDownsync { - toRet := make(map[int32]*PlayerDownsync, 0) +func toPbRoomDownsyncFrame(rdf *battle.RoomDownsyncFrame) *pb.RoomDownsyncFrame { + if nil == rdf { + return nil + } + ret := &pb.RoomDownsyncFrame{ + Id: rdf.Id, + PlayersArr: make([]*pb.PlayerDownsync, len(rdf.PlayersArr), len(rdf.PlayersArr)), + MeleeBullets: make([]*pb.MeleeBullet, len(rdf.MeleeBullets), len(rdf.MeleeBullets)), + CountdownNanos: rdf.CountdownNanos, + BackendUnconfirmedMask: rdf.BackendUnconfirmedMask, + ShouldForceResync: rdf.ShouldForceResync, + PlayerOpPatternToSkillId: make(map[int32]int32), + } + + for i, last := range rdf.PlayersArr { + pbPlayer := &pb.PlayerDownsync{ + Id: last.Id, + VirtualGridX: last.VirtualGridX, + VirtualGridY: last.VirtualGridY, + DirX: last.DirX, + DirY: last.DirY, + VelX: last.VelX, + VelY: last.VelY, + Speed: last.Speed, + BattleState: last.BattleState, + CharacterState: last.CharacterState, + InAir: last.InAir, + JoinIndex: last.JoinIndex, + ColliderRadius: last.ColliderRadius, + Score: last.Score, + FramesToRecover: last.FramesToRecover, + Hp: last.Hp, + MaxHp: last.MaxHp, + Removed: last.Removed, + } + ret.PlayersArr[i] = pbPlayer + } + + for i, last := range rdf.MeleeBullets { + pbBullet := &pb.MeleeBullet{ + BattleLocalId: last.BattleLocalId, + StartupFrames: last.StartupFrames, + 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, + SelfMoveforwardY: last.SelfMoveforwardY, + HitboxSizeX: last.HitboxSizeX, + HitboxSizeY: last.HitboxSizeY, + + OffenderJoinIndex: last.OffenderJoinIndex, + OffenderPlayerId: last.OffenderPlayerId, + } + ret.MeleeBullets[i] = pbBullet + } + + for i, last := range rdf.PlayerOpPatternToSkillId { + ret.PlayerOpPatternToSkillId[int32(i)] = int32(last) + } + + return ret +} + +func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) []*pb.PlayerDownsync { + toRet := make([]*pb.PlayerDownsync, len(modelInstances), len(modelInstances)) if nil == modelInstances { return toRet } - for k, last := range modelInstances { - toRet[k] = &PlayerDownsync{ + for _, last := range modelInstances { + pbPlayer := &pb.PlayerDownsync{ + Id: last.Id, + VirtualGridX: last.VirtualGridX, + VirtualGridY: last.VirtualGridY, + DirX: last.DirX, + DirY: last.DirY, + VelX: last.VelX, + VelY: last.VelY, + Speed: last.Speed, + BattleState: last.BattleState, + CharacterState: last.CharacterState, + InAir: last.InAir, + JoinIndex: last.JoinIndex, + ColliderRadius: last.ColliderRadius, + Score: last.Score, + Removed: last.Removed, + FramesToRecover: last.FramesToRecover, + } + if withMetaInfo { + pbPlayer.Name = last.Name + pbPlayer.DisplayName = last.DisplayName + pbPlayer.Avatar = last.Avatar + } + toRet[last.JoinIndex-1] = pbPlayer + } + + return toRet +} + +func toJsPlayers(modelInstances map[int32]*Player) []*battle.PlayerDownsync { + toRet := make([]*battle.PlayerDownsync, len(modelInstances), len(modelInstances)) + if nil == modelInstances { + return toRet + } + + for _, last := range modelInstances { + toRet[last.JoinIndex-1] = &battle.PlayerDownsync{ Id: last.Id, VirtualGridX: last.VirtualGridX, VirtualGridY: last.VirtualGridY, @@ -28,11 +136,6 @@ func toPbPlayers(modelInstances map[int32]*Player, withMetaInfo bool) map[int32] Score: last.Score, Removed: last.Removed, } - if withMetaInfo { - toRet[k].Name = last.Name - toRet[k].DisplayName = last.DisplayName - toRet[k].Avatar = last.Avatar - } } return toRet diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index 34b3cfb..8370e58 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -3,19 +3,19 @@ package models import ( . "battle_srv/common" "battle_srv/common/utils" - . "battle_srv/protos" + pb "battle_srv/protos" . "dnmshared" - . "dnmshared/sharedprotos" "encoding/xml" "fmt" "github.com/golang/protobuf/proto" "github.com/gorilla/websocket" - "github.com/solarlune/resolv" "go.uber.org/zap" "io/ioutil" + "jsexport/battle" "math/rand" "os" "path/filepath" + "resolv" "strings" "sync" "sync/atomic" @@ -43,50 +43,15 @@ const ( MAGIC_JOIN_INDEX_INVALID = -1 ) -const ( - COLLISION_CATEGORY_CONTROLLED_PLAYER = (1 << 1) - COLLISION_CATEGORY_BARRIER = (1 << 2) - - COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_BARRIER) - COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_CONTROLLED_PLAYER) - - COLLISION_PLAYER_INDEX_PREFIX = (1 << 17) - COLLISION_BARRIER_INDEX_PREFIX = (1 << 16) - COLLISION_BULLET_INDEX_PREFIX = (1 << 15) -) - const ( MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED = -1 MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED = -2 ) -const ( - ATK_CHARACTER_STATE_IDLE1 = int32(0) - ATK_CHARACTER_STATE_WALKING = int32(1) - ATK_CHARACTER_STATE_ATK1 = int32(2) - ATK_CHARACTER_STATE_ATKED1 = int32(3) - ATK_CHARACTER_STATE_INAIR_IDLE1 = int32(4) - ATK_CHARACTER_STATE_INAIR_ATK1 = int32(5) - ATK_CHARACTER_STATE_INAIR_ATKED1 = int32(6) -) - const ( DEFAULT_PLAYER_RADIUS = float64(12) ) -// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged. -var DIRECTION_DECODER = [][]int32{ - {0, 0}, - {0, +2}, - {0, -2}, - {+2, 0}, - {-2, 0}, - {+1, +1}, - {-1, -1}, - {+1, -1}, - {-1, +1}, -} - type RoomBattleState struct { IDLE int32 WAITING int32 @@ -123,14 +88,15 @@ func calRoomScore(inRoomPlayerCount int32, roomPlayerCnt int, currentRoomBattleS } type Room struct { - Id int32 - Capacity int - collisionSpaceOffsetX float64 - collisionSpaceOffsetY float64 - Players map[int32]*Player - PlayersArr []*Player // ordered by joinIndex - Space *resolv.Space - CollisionSysMap map[int32]*resolv.Object + Id int32 + Capacity int + collisionSpaceOffsetX float64 + collisionSpaceOffsetY float64 + playerOpPatternToSkillId map[int]int + Players map[int32]*Player + PlayersArr []*Player // ordered by joinIndex + Space *resolv.Space + CollisionSysMap map[int32]*resolv.Object /** * The following `PlayerDownsyncSessionDict` is NOT individually put * under `type Player struct` for a reason. @@ -150,7 +116,7 @@ type Room struct { * Moreover, during the invocation of `PlayerSignalToCloseDict`, the `Player` instance is supposed to be deallocated (though not synchronously). */ PlayerDownsyncSessionDict map[int32]*websocket.Conn - PlayerDownsyncChanDict map[int32](chan InputsBufferSnapshot) + PlayerDownsyncChanDict map[int32](chan pb.InputsBufferSnapshot) PlayerActiveWatchdogDict map[int32](*Watchdog) PlayerSignalToCloseDict map[int32]SignalToCloseConnCbType Score float32 @@ -160,10 +126,9 @@ type Room struct { CurDynamicsRenderFrameId int32 // [WARNING] The dynamics of backend is ALWAYS MOVING FORWARD BY ALL-CONFIRMED INPUTFRAMES (either by upsync or forced), i.e. no rollback; Moreover when "true == BackendDynamicsEnabled" we always have "Room.CurDynamicsRenderFrameId >= Room.RenderFrameId" because each "all-confirmed inputFrame" is applied on "all applicable renderFrames" in one-go hence often sees a future "renderFrame" earlier EffectivePlayerCount int32 DismissalWaitGroup sync.WaitGroup - Barriers map[int32]*Barrier - InputsBuffer *RingBuffer // Indices are STRICTLY consecutive - InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange] - RenderFrameBuffer *RingBuffer // Indices are STRICTLY consecutive + InputsBuffer *battle.RingBuffer // Indices are STRICTLY consecutive + InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange] + RenderFrameBuffer *battle.RingBuffer // Indices are STRICTLY consecutive LatestPlayerUpsyncedInputFrameId int32 LastAllConfirmedInputFrameId int32 LastAllConfirmedInputFrameIdWithChange int32 @@ -178,7 +143,12 @@ type Room struct { BulletBattleLocalIdCounter int32 dilutedRollbackEstimatedDtNanos int64 - BattleColliderInfo // Compositing to send centralized magic numbers + pb.BattleColliderInfo // Compositing to send centralized magic numbers + + TmxPointsMap StrToVec2DListMap + TmxPolygonsMap StrToPolygon2DListMap + + rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync } func (pR *Room) updateScore() { @@ -317,28 +287,14 @@ func (pR *Room) ChooseStage() error { panic(err) } + //Logger.Info("parsed tmx:", zap.Any("stageDiscreteW", stageDiscreteW), zap.Any("strToVec2DListMap", strToVec2DListMap), zap.Any("strToPolygon2DListMap", strToPolygon2DListMap)) + pR.StageDiscreteW = stageDiscreteW pR.StageDiscreteH = stageDiscreteH pR.StageTileW = stageTileW pR.StageTileH = stageTileH - pR.StrToVec2DListMap = strToVec2DListMap - pR.StrToPolygon2DListMap = strToPolygon2DListMap - - barrierPolygon2DList := *(strToPolygon2DListMap["Barrier"]) - - var barrierLocalIdInBattle int32 = 0 - for _, polygon2DUnaligned := range barrierPolygon2DList.Eles { - polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned) - /* - // 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)) - */ - pR.Barriers[barrierLocalIdInBattle] = &Barrier{ - Boundary: polygon2D, - } - - barrierLocalIdInBattle++ - } + pR.TmxPointsMap = strToVec2DListMap + pR.TmxPolygonsMap = strToPolygon2DListMap return nil } @@ -379,7 +335,7 @@ func (pR *Room) InputsBufferString(allDetails bool) string { if nil == tmp { break } - f := tmp.(*InputFrameDownsync) + f := tmp.(*pb.InputFrameDownsync) s = append(s, fmt.Sprintf("{\"inputFrameId\":%d,\"inputList\":%v,\"confirmedList\":\"%d\"}", f.InputFrameId, f.InputList, f.ConfirmedList)) } @@ -389,6 +345,50 @@ func (pR *Room) InputsBufferString(allDetails bool) string { } } +func (pR *Room) playerDownsyncStr(player *battle.PlayerDownsync) string { + if nil == player { + return "" + } + inAirInt := 0 + if player.InAir { + 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) + + return s +} + +func (pR *Room) inputFrameDownsyncStr(inputFrameDownsync *pb.InputFrameDownsync) string { + if nil == inputFrameDownsync { + return "" + } + s := make([]string, 0) + s = append(s, fmt.Sprintf("InputFrameId:%d", inputFrameDownsync.InputFrameId)) + ss := make([]string, 0) + for _, v := range inputFrameDownsync.InputList { + ss = append(ss, fmt.Sprintf("\"%d\"", v)) + } + s = append(s, fmt.Sprintf("InputList:[%v]", strings.Join(ss, ","))) + //s = append(s, fmt.Sprintf("ConfirmedList:%d", inputFrameDownsync.ConfirmedList)) + + return strings.Join(s, ",") +} + +func (pR *Room) rdfIdToActuallyUsedInputString() string { + // Appending of the array of strings can be very SLOW due to on-demand heap allocation! Use this printing with caution. + s := make([]string, 0) + for rdfId := pR.RenderFrameBuffer.StFrameId; rdfId < pR.RenderFrameBuffer.EdFrameId; rdfId++ { + rdf := pR.RenderFrameBuffer.GetByFrameId(rdfId).(*battle.RoomDownsyncFrame) + playersStrBldr := make([]string, 0, len(rdf.PlayersArr)) + for _, player := range rdf.PlayersArr { + playersStrBldr = append(playersStrBldr, pR.playerDownsyncStr(player)) + } + s = append(s, fmt.Sprintf("rdfId:%d\nplayers:[%v]\nactuallyUsedinputList:{%v}", rdfId, strings.Join(playersStrBldr, ","), pR.inputFrameDownsyncStr(pR.rdfIdToActuallyUsedInput[rdfId]))) + } + + return strings.Join(s, "\n") +} + func (pR *Room) StartBattle() { if RoomBattleStateIns.WAITING != pR.State { Logger.Debug("[StartBattle] Battle not started due to not being WAITING!", zap.Any("roomId", pR.Id), zap.Any("roomState", pR.State)) @@ -397,14 +397,21 @@ func (pR *Room) StartBattle() { pR.RenderFrameId = 0 + // [WARNING] Only since battle starts do we have all players bound to certain joinIndexes. + for _, player := range pR.Players { + opJoinIndexPrefix := (int(player.JoinIndex) << uint(8)) + pR.playerOpPatternToSkillId[opJoinIndexPrefix+0] = 1 // Hardcoded for now + } + // Initialize the "collisionSys" as well as "RenderFrameBuffer" pR.CurDynamicsRenderFrameId = 0 - kickoffFrame := &RoomDownsyncFrame{ - Id: pR.RenderFrameId, - Players: toPbPlayers(pR.Players, false), - CountdownNanos: pR.BattleDurationNanos, + kickoffFrameJs := &battle.RoomDownsyncFrame{ + Id: pR.RenderFrameId, + PlayersArr: toJsPlayers(pR.Players), + PlayerOpPatternToSkillId: pR.playerOpPatternToSkillId, + CountdownNanos: pR.BattleDurationNanos, } - pR.RenderFrameBuffer.Put(kickoffFrame) + pR.RenderFrameBuffer.Put(kickoffFrameJs) // Refresh "Colliders" spaceW := pR.StageDiscreteW * pR.StageTileW @@ -426,6 +433,10 @@ func (pR *Room) StartBattle() { } 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 + if pR.FrameDataLoggingEnabled { + rdfIdToActuallyUsedInputDump := pR.rdfIdToActuallyUsedInputString() + os.WriteFile(fmt.Sprintf("room_%d.txt", pR.Id), []byte(rdfIdToActuallyUsedInputDump), 0644) // DEBUG ONLY + } pR.onBattleStoppedForSettlement() }() @@ -469,8 +480,8 @@ func (pR *Room) StartBattle() { case PlayerBattleStateIns.DISCONNECTED, PlayerBattleStateIns.LOST, PlayerBattleStateIns.EXPELLED_DURING_GAME, PlayerBattleStateIns.EXPELLED_IN_DISMISSAL: continue } - kickoffFrame := pR.RenderFrameBuffer.GetByFrameId(0).(*RoomDownsyncFrame) - pR.sendSafely(kickoffFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true) + kickoffFrameJs := pR.RenderFrameBuffer.GetByFrameId(0).(*battle.RoomDownsyncFrame) + pR.sendSafely(toPbRoomDownsyncFrame(kickoffFrameJs), nil, DOWNSYNC_MSG_ACT_BATTLE_START, playerId, true) } Logger.Info(fmt.Sprintf("In `battleMainLoop` for roomId=%v sent out kickoffFrame", pR.Id)) } @@ -497,7 +508,7 @@ func (pR *Room) StartBattle() { } } - downsyncLoop := func(playerId int32, player *Player, playerDownsyncChan chan InputsBufferSnapshot) { + downsyncLoop := func(playerId int32, player *Player, playerDownsyncChan chan pb.InputsBufferSnapshot) { defer func() { if r := recover(); r != nil { Logger.Error("downsyncLoop, recovery spot#1, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("panic", r)) @@ -529,7 +540,7 @@ func (pR *Room) StartBattle() { Each "playerDownsyncChan" stays alive through out the lifecycle of room instead of each "playerDownsyncSession", i.e. not closed or dereferenced upon disconnection. */ - pR.PlayerDownsyncChanDict[playerId] = make(chan InputsBufferSnapshot, pR.InputsBuffer.N) + pR.PlayerDownsyncChanDict[playerId] = make(chan pb.InputsBufferSnapshot, pR.InputsBuffer.N) go downsyncLoop(playerId, player, pR.PlayerDownsyncChanDict[playerId]) } @@ -543,7 +554,7 @@ func (pR *Room) toDiscreteInputsBufferIndex(inputFrameId int32, joinIndex int32) return (inputFrameId << 2) + joinIndex // allowing joinIndex upto 15 } -func (pR *Room) OnBattleCmdReceived(pReq *WsReq) { +func (pR *Room) OnBattleCmdReceived(pReq *pb.WsReq) { /* [WARNING] This function "OnBattleCmdReceived" could be called by different ws sessions and thus from different threads! @@ -594,7 +605,7 @@ func (pR *Room) OnBattleCmdReceived(pReq *WsReq) { } } -func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *InputFrameDownsync, playerId int32) { +func (pR *Room) onInputFrameDownsyncAllConfirmed(inputFrameDownsync *battle.InputFrameDownsync, playerId int32) { // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! inputFrameId := inputFrameDownsync.InputFrameId if -1 == pR.LastAllConfirmedInputFrameIdWithChange || false == pR.equalInputLists(inputFrameDownsync.InputList, pR.LastAllConfirmedInputList) { @@ -637,9 +648,9 @@ func (pR *Room) StopBattleForSettlement() { Logger.Info("Stopping the `battleMainLoop` for:", zap.Any("roomId", pR.Id)) pR.RenderFrameId++ for playerId, _ := range pR.Players { - assembledFrame := RoomDownsyncFrame{ + assembledFrame := pb.RoomDownsyncFrame{ Id: pR.RenderFrameId, - Players: toPbPlayers(pR.Players, false), + PlayersArr: toPbPlayers(pR.Players, false), CountdownNanos: -1, // TODO: Replace this magic constant! } pR.sendSafely(&assembledFrame, nil, DOWNSYNC_MSG_ACT_BATTLE_STOPPED, playerId, true) @@ -663,9 +674,9 @@ func (pR *Room) onBattlePrepare(cb BattleStartCbType) { pR.State = RoomBattleStateIns.PREPARE Logger.Info("Battle state transitted to RoomBattleStateIns.PREPARE for:", zap.Any("roomId", pR.Id)) - battleReadyToStartFrame := &RoomDownsyncFrame{ + battleReadyToStartFrame := &pb.RoomDownsyncFrame{ Id: DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START, - Players: toPbPlayers(pR.Players, true), + PlayersArr: toPbPlayers(pR.Players, true), CountdownNanos: pR.BattleDurationNanos, } @@ -733,10 +744,12 @@ func (pR *Room) OnDismissed() { // Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference. pR.BulletBattleLocalIdCounter = 0 - pR.WorldToVirtualGridRatio = float64(1000) + pR.WorldToVirtualGridRatio = float64(100) 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.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.PlayersArr = make([]*Player, pR.Capacity) pR.CollisionSysMap = make(map[int32]*resolv.Object) @@ -748,13 +761,13 @@ func (pR *Room) OnDismissed() { for _, oldChan := range pR.PlayerDownsyncChanDict { close(oldChan) } - pR.PlayerDownsyncChanDict = make(map[int32](chan InputsBufferSnapshot)) + pR.PlayerDownsyncChanDict = make(map[int32](chan pb.InputsBufferSnapshot)) pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType) pR.JoinIndexBooleanArr = make([]bool, pR.Capacity) - pR.Barriers = make(map[int32]*Barrier) pR.RenderCacheSize = 1024 - pR.RenderFrameBuffer = NewRingBuffer(pR.RenderCacheSize) - pR.InputsBuffer = NewRingBuffer((pR.RenderCacheSize >> 1) + 1) + pR.RenderFrameBuffer = battle.NewRingBuffer(pR.RenderCacheSize) + pR.InputsBuffer = battle.NewRingBuffer((pR.RenderCacheSize >> 1) + 1) + pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync) pR.LatestPlayerUpsyncedInputFrameId = -1 pR.LastAllConfirmedInputFrameId = -1 @@ -778,39 +791,14 @@ func (pR *Room) OnDismissed() { pR.BackendDynamicsEnabled = true // [WARNING] When "false", recovery upon reconnection wouldn't work! pR.ForceAllResyncOnAnyActiveSlowTicker = true // See tradeoff discussion in "downsyncToAllPlayers" - punchSkillId := int32(1) - pR.MeleeSkillConfig = make(map[int32]*MeleeBullet, 0) - pR.MeleeSkillConfig[punchSkillId] = &MeleeBullet{ - // for offender - StartupFrames: int32(10), - ActiveFrames: int32(10), - RecoveryFrames: int32(34), - RecoveryFramesOnBlock: int32(34), - RecoveryFramesOnHit: int32(34), - Moveforward: &Vec2D{ - X: 0, - Y: 0, - }, - HitboxOffset: float64(12.0), // should be about the radius of the PlayerCollider - HitboxSize: &Vec2D{ - X: float64(24.0), - Y: float64(32.0), - }, - - // for defender - HitStunFrames: int32(18), - BlockStunFrames: int32(9), - Pushback: float64(8.0), - ReleaseTriggerType: int32(1), // 1: rising-edge, 2: falling-edge - Damage: int32(5), - } - pR.SnapIntoPlatformOverlap = float64(0.1) pR.SnapIntoPlatformThreshold = float64(0.5) pR.JumpingInitVelY = int32(float64(7) * pR.WorldToVirtualGridRatio) pR.GravityX = 0 pR.GravityY = -int32(float64(0.5) * pR.WorldToVirtualGridRatio) // makes all "playerCollider.Y" a multiple of 0.5 in all cases + pR.FrameDataLoggingEnabled = false // [WARNING] DON'T ENABLE ON LONG BATTLE DURATION! It consumes A LOT OF MEMORY! + pR.ChooseStage() pR.EffectivePlayerCount = 0 @@ -927,16 +915,16 @@ func (pR *Room) onPlayerAdded(playerId int32) { pR.JoinIndexBooleanArr[index] = true // Lazily assign the initial position of "Player" for "RoomDownsyncFrame". - playerPosList := *(pR.StrToVec2DListMap["PlayerStartingPos"]) - if index > len(playerPosList.Eles) { + playerPosList := *pR.TmxPointsMap["PlayerStartingPos"] + if index > len(playerPosList) { panic(fmt.Sprintf("onPlayerAdded error, index >= len(playerPosList), roomId=%v, playerId=%v, roomState=%v, roomEffectivePlayerCount=%v", pR.Id, playerId, pR.State, pR.EffectivePlayerCount)) } - playerPos := playerPosList.Eles[index] + playerPos := playerPosList[index] 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)) } - pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio) + pR.Players[playerId].VirtualGridX, pR.Players[playerId].VirtualGridY = battle.WorldToVirtualGridPos(playerPos.X, playerPos.Y, pR.WorldToVirtualGridRatio) // Hardcoded initial character orientation/facing if 0 == (pR.Players[playerId].JoinIndex % 2) { pR.Players[playerId].DirX = -2 @@ -976,9 +964,9 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool { targetPlayerBattleState := atomic.LoadInt32(&(targetPlayer.BattleState)) switch targetPlayerBattleState { case PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK: - playerAckedFrame := &RoomDownsyncFrame{ - Id: pR.RenderFrameId, - Players: toPbPlayers(pR.Players, true), + playerAckedFrame := &pb.RoomDownsyncFrame{ + Id: pR.RenderFrameId, + PlayersArr: toPbPlayers(pR.Players, true), } // Broadcast normally added player info to all players in the same room @@ -1029,7 +1017,7 @@ func (pR *Room) OnPlayerBattleColliderAcked(playerId int32) bool { return true } -func (pR *Room) sendSafely(roomDownsyncFrame *RoomDownsyncFrame, toSendInputFrameDownsyncs []*InputFrameDownsync, act int32, playerId int32, needLockExplicitly bool) { +func (pR *Room) sendSafely(roomDownsyncFrame *pb.RoomDownsyncFrame, toSendInputFrameDownsyncs []*pb.InputFrameDownsync, act int32, playerId int32, needLockExplicitly bool) { defer func() { if r := recover(); r != nil { Logger.Error("sendSafely, recovered from: ", zap.Any("roomId", pR.Id), zap.Any("playerId", playerId), zap.Any("panic", r)) @@ -1037,7 +1025,7 @@ func (pR *Room) sendSafely(roomDownsyncFrame *RoomDownsyncFrame, toSendInputFram }() if playerDownsyncSession, existent := pR.PlayerDownsyncSessionDict[playerId]; existent { - pResp := &WsResp{ + pResp := &pb.WsResp{ Ret: int32(Constants.RetCode.Ok), Act: act, Rdf: roomDownsyncFrame, @@ -1064,7 +1052,7 @@ func (pR *Room) shouldPrefabInputFrameDownsync(prevRenderFrameId int32, renderFr return false, -1 } -func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *InputFrameDownsync { +func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *battle.InputFrameDownsync { /* [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked. @@ -1073,39 +1061,41 @@ func (pR *Room) getOrPrefabInputFrameDownsync(inputFrameId int32) *InputFrameDow - OR there's no change w.r.t. to its prev cmd. */ - var currInputFrameDownsync *InputFrameDownsync = nil + var currInputFrameDownsync *battle.InputFrameDownsync = nil tmp1 := pR.InputsBuffer.GetByFrameId(inputFrameId) // Would be nil if "pR.InputsBuffer.EdFrameId <= inputFrameId", else if "pR.InputsBuffer.EdFrameId > inputFrameId" is already met, then by now we can just return "tmp1.(*InputFrameDownsync)" if nil == tmp1 { for pR.InputsBuffer.EdFrameId <= inputFrameId { j := pR.InputsBuffer.EdFrameId - currInputFrameDownsync = &InputFrameDownsync{ + currInputFrameDownsync = &battle.InputFrameDownsync{ InputFrameId: j, InputList: make([]uint64, pR.Capacity), ConfirmedList: uint64(0), } j2 := j - 1 - if 0 <= pR.LastAllConfirmedInputFrameId && j2 >= pR.LastAllConfirmedInputFrameId { - j2 = pR.LastAllConfirmedInputFrameId - } tmp2 := pR.InputsBuffer.GetByFrameId(j2) if nil != tmp2 { - prevInputFrameDownsync := tmp2.(*InputFrameDownsync) + prevInputFrameDownsync := tmp2.(*battle.InputFrameDownsync) for i, _ := range currInputFrameDownsync.InputList { - currInputFrameDownsync.InputList[i] = (prevInputFrameDownsync.InputList[i] & uint64(15)) // Don't predict attack input! + currInputFrameDownsync.InputList[i] = prevInputFrameDownsync.InputList[i] } } + for i, _ := range currInputFrameDownsync.InputList { + // Don't predict "btnA & btnB"! + currInputFrameDownsync.InputList[i] = (currInputFrameDownsync.InputList[i] & uint64(15)) + } + pR.InputsBuffer.Put(currInputFrameDownsync) } } else { - currInputFrameDownsync = tmp1.(*InputFrameDownsync) + currInputFrameDownsync = tmp1.(*battle.InputFrameDownsync) } return currInputFrameDownsync } -func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*InputFrameUpsync, playerId int32, player *Player) *InputsBufferSnapshot { +func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFrameUpsync, playerId int32, player *Player) *pb.InputsBufferSnapshot { // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! // Step#1, put the received "inputFrameUpsyncBatch" into "pR.InputsBuffer" for _, inputFrameUpsync := range inputFrameUpsyncBatch { @@ -1145,7 +1135,7 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*InputFrame panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", inputFrameId, pR.Id, pR.InputsBufferString(false))) } shouldBreakConfirmation := false - inputFrameDownsync := tmp.(*InputFrameDownsync) + inputFrameDownsync := tmp.(*battle.InputFrameDownsync) if allConfirmedMask != inputFrameDownsync.ConfirmedList { for _, player := range pR.PlayersArr { @@ -1204,13 +1194,13 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 { if nil == tmp { panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", j, pR.Id, pR.InputsBufferString(false))) } - inputFrameDownsync := tmp.(*InputFrameDownsync) + inputFrameDownsync := tmp.(*battle.InputFrameDownsync) unconfirmedMask |= (allConfirmedMask ^ inputFrameDownsync.ConfirmedList) inputFrameDownsync.ConfirmedList = allConfirmedMask pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1) } if 0 < unconfirmedMask { - Logger.Debug(fmt.Sprintf("[type#1 forceConfirmation] For roomId=%d@renderFrameId=%d, curDynamicsRenderFrameId=%d, LatestPlayerUpsyncedInputFrameId:%d, LastAllConfirmedInputFrameId:%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.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, (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)) } } else { // Type#2 helps resolve the edge case when all players are disconnected temporarily @@ -1231,7 +1221,7 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 { return unconfirmedMask } -func (pR *Room) produceInputsBufferSnapshotWithCurDynamicsRenderFrameAsRef(unconfirmedMask uint64, snapshotStFrameId, snapshotEdFrameId int32) *InputsBufferSnapshot { +func (pR *Room) produceInputsBufferSnapshotWithCurDynamicsRenderFrameAsRef(unconfirmedMask uint64, snapshotStFrameId, snapshotEdFrameId int32) *pb.InputsBufferSnapshot { // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! refRenderFrameIdIfNeeded := pR.CurDynamicsRenderFrameId - 1 if 0 > refRenderFrameIdIfNeeded { @@ -1240,7 +1230,7 @@ func (pR *Room) produceInputsBufferSnapshotWithCurDynamicsRenderFrameAsRef(uncon // Duplicate downsynced inputFrameIds will be filtered out by frontend. toSendInputFrameDownsyncs := pR.cloneInputsBuffer(snapshotStFrameId, snapshotEdFrameId) - return &InputsBufferSnapshot{ + return &pb.InputsBufferSnapshot{ RefRenderFrameId: refRenderFrameIdIfNeeded, UnconfirmedMask: unconfirmedMask, ToSendInputFrameDownsyncs: toSendInputFrameDownsyncs, @@ -1254,17 +1244,14 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende } Logger.Debug(fmt.Sprintf("Applying inputFrame dynamics: roomId=%v, room.RenderFrameId=%v, fromRenderFrameId=%v, toRenderFrameId=%v", pR.Id, pR.RenderFrameId, fromRenderFrameId, toRenderFrameId)) - totPlayerCnt := uint32(pR.Capacity) - allConfirmedMask := uint64((1 << totPlayerCnt) - 1) for collisionSysRenderFrameId := fromRenderFrameId; collisionSysRenderFrameId < toRenderFrameId; collisionSysRenderFrameId++ { currRenderFrameTmp := pR.RenderFrameBuffer.GetByFrameId(collisionSysRenderFrameId) if nil == currRenderFrameTmp { panic(fmt.Sprintf("collisionSysRenderFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v)! RenderFrameBuffer=%v", collisionSysRenderFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, pR.RenderFrameBufferString())) } - currRenderFrame := currRenderFrameTmp.(*RoomDownsyncFrame) + currRenderFrame := currRenderFrameTmp.(*battle.RoomDownsyncFrame) delayedInputFrameId := pR.ConvertToInputFrameId(collisionSysRenderFrameId, pR.InputDelayFrames) - var delayedInputFrame *InputFrameDownsync = nil if 0 <= delayedInputFrameId { 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))) @@ -1273,406 +1260,57 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende if nil == tmp { panic(fmt.Sprintf("delayedInputFrameId=%v doesn't exist for roomId=%v, this is abnormal because it's to be used for applying dynamics to [fromRenderFrameId:%v, toRenderFrameId:%v) @ collisionSysRenderFrameId=%v! InputsBuffer=%v", delayedInputFrameId, pR.Id, fromRenderFrameId, toRenderFrameId, collisionSysRenderFrameId, pR.InputsBufferString(false))) } - delayedInputFrame = tmp.(*InputFrameDownsync) - // [WARNING] It's possible that by now "allConfirmedMask != delayedInputFrame.ConfirmedList && delayedInputFrameId <= pR.LastAllConfirmedInputFrameId", we trust "pR.LastAllConfirmedInputFrameId" as the TOP AUTHORITY. - delayedInputFrame.ConfirmedList = allConfirmedMask + + if pR.FrameDataLoggingEnabled { + delayedInputFrame := tmp.(*battle.InputFrameDownsync) + actuallyUsedInputClone := make([]uint64, len(delayedInputFrame.InputList), len(delayedInputFrame.InputList)) + for i, v := range delayedInputFrame.InputList { + actuallyUsedInputClone[i] = v + } + pR.rdfIdToActuallyUsedInput[currRenderFrame.Id] = &pb.InputFrameDownsync{ + InputFrameId: delayedInputFrame.InputFrameId, + InputList: actuallyUsedInputClone, + ConfirmedList: delayedInputFrame.ConfirmedList, + } + } } - nextRenderFrame := pR.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, pR.CollisionSysMap) + 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) pR.RenderFrameBuffer.Put(nextRenderFrame) pR.CurDynamicsRenderFrameId++ } } -// TODO: Write unit-test for this function to compare with its frontend counter part -func (pR *Room) applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame *InputFrameDownsync, currRenderFrame *RoomDownsyncFrame, collisionSysMap map[int32]*resolv.Object) *RoomDownsyncFrame { - topPadding, bottomPadding, leftPadding, rightPadding := pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap, pR.SnapIntoPlatformOverlap - // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! - nextRenderFramePlayers := make(map[int32]*PlayerDownsync, pR.Capacity) - // Make a copy first - for playerId, currPlayerDownsync := range currRenderFrame.Players { - nextRenderFramePlayers[playerId] = &PlayerDownsync{ - Id: playerId, - VirtualGridX: currPlayerDownsync.VirtualGridX, - VirtualGridY: currPlayerDownsync.VirtualGridY, - DirX: currPlayerDownsync.DirX, - DirY: currPlayerDownsync.DirY, - VelX: currPlayerDownsync.VelX, - VelY: currPlayerDownsync.VelY, - CharacterState: currPlayerDownsync.CharacterState, - InAir: true, - Speed: currPlayerDownsync.Speed, - BattleState: currPlayerDownsync.BattleState, - Score: currPlayerDownsync.Score, - Removed: currPlayerDownsync.Removed, - JoinIndex: currPlayerDownsync.JoinIndex, - FramesToRecover: currPlayerDownsync.FramesToRecover - 1, - Hp: currPlayerDownsync.Hp, - MaxHp: currPlayerDownsync.MaxHp, - } - if nextRenderFramePlayers[playerId].FramesToRecover < 0 { - nextRenderFramePlayers[playerId].FramesToRecover = 0 - } - } - - 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, pR.Capacity) - hardPushbackNorms := make([][]Vec2D, pR.Capacity) - - // 1. Process player inputs - if nil != delayedInputFrame { - var delayedInputFrameForPrevRenderFrame *InputFrameDownsync = nil - tmp := pR.InputsBuffer.GetByFrameId(pR.ConvertToInputFrameId(currRenderFrame.Id-1, pR.InputDelayFrames)) - if nil != tmp { - delayedInputFrameForPrevRenderFrame = tmp.(*InputFrameDownsync) - } - inputList := delayedInputFrame.InputList - for _, player := range pR.PlayersArr { - playerId := player.Id - joinIndex := player.JoinIndex - currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] - if 0 < thatPlayerInNextFrame.FramesToRecover { - continue - } - decodedInput := pR.decodeInput(inputList[joinIndex-1]) - prevBtnALevel, prevBtnBLevel := int32(0), int32(0) - if nil != delayedInputFrameForPrevRenderFrame { - prevDecodedInput := pR.decodeInput(delayedInputFrameForPrevRenderFrame.InputList[joinIndex-1]) - prevBtnALevel = prevDecodedInput.BtnALevel - prevBtnBLevel = prevDecodedInput.BtnBLevel - } - - if decodedInput.BtnBLevel > prevBtnBLevel { - characStateAlreadyInAir := false - if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame.CharacterState { - characStateAlreadyInAir = true - } - characStateIsInterruptWaivable := false - if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame.CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame.CharacterState { - characStateIsInterruptWaivable = true - } - if !characStateAlreadyInAir && characStateIsInterruptWaivable { - thatPlayerInNextFrame.VelY = pR.JumpingInitVelY - if 1 == currPlayerDownsync.JoinIndex { - Logger.Info(fmt.Sprintf("playerId=%v, joinIndex=%v jumped at {renderFrame.id: %d, virtualX: %d, virtualY: %d, nextVelX: %d, nextVelY: %d, nextCharacterState=%d, inAir=%v}, delayedInputFrame.id=%d", playerId, joinIndex, currRenderFrame.Id, currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, thatPlayerInNextFrame.CharacterState, currPlayerDownsync.InAir, delayedInputFrame.InputFrameId)) - } - } - } - - if decodedInput.BtnALevel > prevBtnALevel { - punchSkillId := int32(1) - punchConfig := pR.MeleeSkillConfig[punchSkillId] - var newMeleeBullet MeleeBullet = *punchConfig - newMeleeBullet.BattleLocalId = pR.BulletBattleLocalIdCounter - pR.BulletBattleLocalIdCounter += 1 - newMeleeBullet.OffenderJoinIndex = joinIndex - newMeleeBullet.OffenderPlayerId = playerId - newMeleeBullet.OriginatedRenderFrameId = currRenderFrame.Id - nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, &newMeleeBullet) - thatPlayerInNextFrame.FramesToRecover = newMeleeBullet.RecoveryFrames - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATK1 - if false == currPlayerDownsync.InAir { - thatPlayerInNextFrame.VelX = 0 - } - Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a rising-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId)) - - } else if decodedInput.BtnALevel < prevBtnALevel { - Logger.Debug(fmt.Sprintf("roomId=%v, playerId=%v triggered a falling-edge of btnA at currRenderFrame.id=%v, delayedInputFrame.id=%v", pR.Id, playerId, currRenderFrame.Id, delayedInputFrame.InputFrameId)) - } else { - // No bullet trigger, process movement inputs - // Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs - if 0 != decodedInput.Dx || 0 != decodedInput.Dy { - thatPlayerInNextFrame.DirX = decodedInput.Dx - thatPlayerInNextFrame.DirY = decodedInput.Dy - thatPlayerInNextFrame.VelX = decodedInput.Dx * currPlayerDownsync.Speed - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING - } else { - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 - thatPlayerInNextFrame.VelX = 0 - } - } - } - } - - // 2. Process player movement - for _, player := range pR.PlayersArr { - playerId := player.Id - joinIndex := player.JoinIndex - effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y = float64(0), float64(0) - collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex - playerCollider := collisionSysMap[collisionPlayerIndex] - currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] - // Reset playerCollider position from the "virtual grid position" - newVx, newVy := currPlayerDownsync.VirtualGridX+currPlayerDownsync.VelX, currPlayerDownsync.VirtualGridY+currPlayerDownsync.VelY - if thatPlayerInNextFrame.VelY == pR.JumpingInitVelY { - newVy += thatPlayerInNextFrame.VelY - } - - halfColliderWidth, halfColliderHeight := player.ColliderRadius, player.ColliderRadius+player.ColliderRadius // avoid multiplying - playerCollider.X, playerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.VirtualGridToWorldRatio) - // Update in the collision system - playerCollider.Update() - - if currPlayerDownsync.InAir { - thatPlayerInNextFrame.VelX += pR.GravityX - thatPlayerInNextFrame.VelY += pR.GravityY - } - } - - // 3. Add bullet colliders into collision system - bulletColliders := make(map[int32]*resolv.Object, 0) // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible - removedBulletsAtCurrFrame := make(map[int32]int32, 0) - for _, meleeBullet := range currRenderFrame.MeleeBullets { - if (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames <= currRenderFrame.Id) && (meleeBullet.OriginatedRenderFrameId+meleeBullet.StartupFrames+meleeBullet.ActiveFrames > currRenderFrame.Id) { - collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId - collisionOffenderIndex := COLLISION_PLAYER_INDEX_PREFIX + meleeBullet.OffenderJoinIndex - offenderCollider := collisionSysMap[collisionOffenderIndex] - offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId] - - xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" - if 0 > offender.DirX { - xfac = float64(-1.0) - } - offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, pR.VirtualGridToWorldRatio) - bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "MeleeBullet") - newBulletCollider.Data = meleeBullet - pR.Space.Add(newBulletCollider) - collisionSysMap[collisionBulletIndex] = newBulletCollider - bulletColliders[collisionBulletIndex] = newBulletCollider - - Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet is added to collisionSys at currRenderFrame.id=%v as start-up frames ended and active frame is not yet ended: %v, from offenderCollider=%v, xfac=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(newBulletCollider.Shape.(*resolv.ConvexPolygon)), ConvexPolygonStr(offenderCollider.Shape.(*resolv.ConvexPolygon)), xfac)) - } - } - - // 4. Invoke collision system stepping (no-op for backend collision lib) - - // 5. Calc pushbacks for each player (after its movement) w/o bullets - for _, player := range pR.PlayersArr { - joinIndex := player.JoinIndex - playerId := player.Id - collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex - playerCollider := collisionSysMap[collisionPlayerIndex] - playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) - hardPushbackNorms[joinIndex-1] = pR.calcHardPushbacksNorms(playerCollider, playerShape, pR.SnapIntoPlatformOverlap, &(effPushbacks[joinIndex-1])) - currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] - fallStopping := false - possiblyFallStoppedOnAnotherPlayer := false - if collision := playerCollider.Check(0, 0); nil != collision { - for _, obj := range collision.Objects { - isBarrier, isAnotherPlayer, isBullet := false, false, false - switch obj.Data.(type) { - case *Barrier: - isBarrier = true - case *Player: - isAnotherPlayer = true - case *MeleeBullet: - isBullet = true - } - if isBullet { - // ignore bullets for this step - continue - } - bShape := obj.Shape.(*resolv.ConvexPolygon) - overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape) - if !overlapped { - continue - } - normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0)) - landedOnGravityPushback := (pR.SnapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides - if landedOnGravityPushback { - // kindly note that one player might land on top of another player, and snapping is also required in such case - pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap)*overlapResult.OverlapY - thatPlayerInNextFrame.InAir = false - } - if isAnotherPlayer { - // [WARNING] See comments of this substep in frontend. - pushbackX, pushbackY = (overlapResult.Overlap-pR.SnapIntoPlatformOverlap*2)*overlapResult.OverlapX, (overlapResult.Overlap-pR.SnapIntoPlatformOverlap*2)*overlapResult.OverlapY - } - for _, hardPushbackNorm := range hardPushbackNorms[joinIndex-1] { - projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y - if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) { - pushbackX -= projectedMagnitude * hardPushbackNorm.X - pushbackY -= projectedMagnitude * hardPushbackNorm.Y - } - } - effPushbacks[joinIndex-1].X += pushbackX - effPushbacks[joinIndex-1].Y += pushbackY - if currPlayerDownsync.InAir && landedOnGravityPushback { - fallStopping = true - if isAnotherPlayer { - possiblyFallStoppedOnAnotherPlayer = true - } - } - if 1 == joinIndex { - halfColliderWidth, halfColliderHeight := player.ColliderRadius, player.ColliderRadius+player.ColliderRadius // avoid multiplying - if fallStopping { - Logger.Debug(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#1\n{renderFrame.id: %d, possiblyFallStoppedOnAnotherPlayer: %v}\nplayerColliderPos=%v, effPushback={%.3f, %.3f}, overlapMag=%.4f", playerId, joinIndex, currRenderFrame.Id, possiblyFallStoppedOnAnotherPlayer, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap)) - } else if currPlayerDownsync.InAir && isBarrier && !landedOnGravityPushback { - //Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by barrier & not landed at {renderFrame.id: %d}\nplayerColliderPos=%v, effPushback={%.3f, %.3f}, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap, len(hardPushbackNorms))) - } else if currPlayerDownsync.InAir && isAnotherPlayer { - //Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d inAir & pushed back by another player\n{renderFrame.id: %d}\nplayerColliderPos=%v, anotherPlayerColliderPos=%v, effPushback={%.3f, %.3f}, landedOnGravityPushback=%v, fallStopping=%v, overlapMag=%.4f, len(hardPushbackNorms)=%d", playerId, joinIndex, currRenderFrame.Id, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), RectCenterStr(obj, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, landedOnGravityPushback, fallStopping, overlapResult.Overlap, len(hardPushbackNorms))) - } - } - } - } - if fallStopping { - thatPlayerInNextFrame.VelX = 0 - thatPlayerInNextFrame.VelY = 0 - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 - thatPlayerInNextFrame.FramesToRecover = 0 - } - if currPlayerDownsync.InAir { - oldNextCharacterState := thatPlayerInNextFrame.CharacterState - switch oldNextCharacterState { - case ATK_CHARACTER_STATE_IDLE1, ATK_CHARACTER_STATE_WALKING: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1 - case ATK_CHARACTER_STATE_ATK1: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1 - case ATK_CHARACTER_STATE_ATKED1: - thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 - } - } - } - - // 6. Check bullet-anything collisions - for _, bulletCollider := range bulletColliders { - shouldRemove := false - meleeBullet := bulletCollider.Data.(*MeleeBullet) - collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId - bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon) - if collision := bulletCollider.Check(0, 0); collision != nil { - offender := currRenderFrame.Players[meleeBullet.OffenderPlayerId] - for _, obj := range collision.Objects { - defenderShape := obj.Shape.(*resolv.ConvexPolygon) - switch t := obj.Data.(type) { - case *Player: - if meleeBullet.OffenderPlayerId != t.Id { - if overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape); overlapped { - joinIndex := t.JoinIndex - xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" - 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 { - //Logger.Debug(fmt.Sprintf("defenderPlayerId=%d, joinIndex=%d reducing bullet pushback={%.3f, %.3f} by {%.3f, %.3f} where hardPushbackNorm={%.3f, %.3f}, projectedMagnitude=%.3f at renderFrame.id=%d", t.Id, joinIndex, pushbackX, pushbackY, projectedMagnitude*hardPushbackNorm.X, projectedMagnitude*hardPushbackNorm.Y, hardPushbackNorm.X, hardPushbackNorm.Y, projectedMagnitude, currRenderFrame.Id)) - pushbackX -= projectedMagnitude * hardPushbackNorm.X - pushbackY -= projectedMagnitude * hardPushbackNorm.Y - } - } - - effPushbacks[joinIndex-1].X += pushbackX - effPushbacks[joinIndex-1].Y += pushbackY - atkedPlayerInCurFrame, atkedPlayerInNextFrame := currRenderFrame.Players[t.Id], nextRenderFramePlayers[t.Id] - atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_ATKED1 - if atkedPlayerInCurFrame.InAir { - atkedPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 - } - oldFramesToRecover := nextRenderFramePlayers[t.Id].FramesToRecover - if meleeBullet.HitStunFrames > oldFramesToRecover { - atkedPlayerInNextFrame.FramesToRecover = meleeBullet.HitStunFrames - } - Logger.Debug(fmt.Sprintf("roomId=%v, a meleeBullet collides w/ player at currRenderFrame.id=%v: b=%v, p=%v", pR.Id, currRenderFrame.Id, ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape))) - } - } - default: - Logger.Debug(fmt.Sprintf("Bullet %v collided with non-player %v: roomId=%v, currRenderFrame.Id=%v, delayedInputFrame.Id=%v, objDataType=%t, objData=%v", ConvexPolygonStr(bulletShape), ConvexPolygonStr(defenderShape), pR.Id, currRenderFrame.Id, delayedInputFrame.InputFrameId, obj.Data, obj.Data)) - } - } - shouldRemove = true - } - if shouldRemove { - removedBulletsAtCurrFrame[collisionBulletIndex] = 1 - } - } - - // [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback - for _, meleeBullet := range currRenderFrame.MeleeBullets { - collisionBulletIndex := COLLISION_BULLET_INDEX_PREFIX + meleeBullet.BattleLocalId - if bulletCollider, existent := collisionSysMap[collisionBulletIndex]; existent { - bulletCollider.Space.Remove(bulletCollider) - delete(collisionSysMap, collisionBulletIndex) - } - if _, existent := removedBulletsAtCurrFrame[collisionBulletIndex]; existent { - continue - } - nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet) - } - - // 7. Get players out of stuck barriers if there's any - for _, player := range pR.PlayersArr { - joinIndex := player.JoinIndex - playerId := player.Id - collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex - playerCollider := collisionSysMap[collisionPlayerIndex] - // Update "virtual grid position" - currPlayerDownsync, thatPlayerInNextFrame := currRenderFrame.Players[playerId], nextRenderFramePlayers[playerId] - halfColliderWidth, halfColliderHeight := player.ColliderRadius, player.ColliderRadius+player.ColliderRadius // avoid multiplying - thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, pR.WorldToVirtualGridRatio) - - if 1 == thatPlayerInNextFrame.JoinIndex { - if currPlayerDownsync.InAir && !thatPlayerInNextFrame.InAir { - Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d fallStopping#2:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) - } else if !currPlayerDownsync.InAir && thatPlayerInNextFrame.InAir { - Logger.Warn(fmt.Sprintf("playerId=%d, joinIndex=%d took off:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) - } else if thatPlayerInNextFrame.InAir && (0 != thatPlayerInNextFrame.VelY) { - //Logger.Info(fmt.Sprintf("playerId=%d, joinIndex=%d inAir trajectory:\n{nextRenderFrame.id: %d, nextVirtualX: %d, nextVirtualY: %d, nextVelX: %d, nextVelY: %d}\n\tcalculated from <- playerColliderPos=%v, effPushback={%.3f, %.3f}", playerId, joinIndex, currRenderFrame.Id+1, thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY, thatPlayerInNextFrame.VelX, thatPlayerInNextFrame.VelY, RectCenterStr(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY), effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y)) - } - } - } - - return &RoomDownsyncFrame{ - Id: currRenderFrame.Id + 1, - Players: nextRenderFramePlayers, - MeleeBullets: nextRenderFrameMeleeBullets, - CountdownNanos: (pR.BattleDurationNanos - int64(currRenderFrame.Id)*pR.RollbackEstimatedDtNanos), - } -} - -func (pR *Room) decodeInput(encodedInput uint64) *InputFrameDecoded { - encodedDirection := (encodedInput & uint64(15)) - btnALevel := int32((encodedInput >> 4) & 1) - btnBLevel := int32((encodedInput >> 5) & 1) - return &InputFrameDecoded{ - Dx: DIRECTION_DECODER[encodedDirection][0], - Dy: DIRECTION_DECODER[encodedDirection][1], - BtnALevel: btnALevel, - BtnBLevel: btnBLevel, - } -} - -func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool { - return 0 == (inputFrameId % 10) -} - func (pR *Room) refreshColliders(spaceW, spaceH int32) { // 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 - minStep := (int(float64(pR.PlayerDefaultSpeed)*pR.VirtualGridToWorldRatio) << 3) // the approx minimum distance a player can move per frame in world coordinate - pR.Space = resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled - for _, player := range pR.Players { - wx, wy := VirtualGridToWorldPos(player.VirtualGridX, player.VirtualGridY, pR.VirtualGridToWorldRatio) + 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 := GenerateRectCollider(wx, wy, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Player") // the coords of all barrier boundaries are multiples of tileWidth(i.e. 16), by adding snapping y-padding when "landedOnGravityPushback" all "playerCollider.Y" would be a multiple of 1.0 - playerCollider.Data = player + playerCollider := 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 - pR.PlayersArr[joinIndex-1] = player - collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex + collisionPlayerIndex := battle.COLLISION_PLAYER_INDEX_PREFIX + joinIndex pR.CollisionSysMap[collisionPlayerIndex] = playerCollider } - for _, barrier := range pR.Barriers { - boundaryUnaligned := barrier.Boundary - barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, "Barrier") - barrierCollider.Data = barrier + for _, player := range pR.Players { + joinIndex := player.JoinIndex + pR.PlayersArr[joinIndex-1] = player + } + + barrierPolygon2DList := *pR.TmxPolygonsMap["Barrier"] + for _, polygon2DUnaligned := range barrierPolygon2DList { + /* + // 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)) + */ + barrierCollider := battle.GenerateConvexPolygonCollider(polygon2DUnaligned, pR.collisionSpaceOffsetX, pR.collisionSpaceOffsetY, nil, "Barrier") pR.Space.Add(barrierCollider) } } @@ -1732,7 +1370,7 @@ func (pR *Room) doBattleMainLoopPerTickBackendDynamicsWithProperLocking(prevRend } } -func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot) { +func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *pb.InputsBufferSnapshot) { /* [WARNING] This function MUST BE called while "pR.InputsBufferLock" is LOCKED to **preserve the order of generation of "inputsBufferSnapshot" for sending** -- see comments in "OnBattleCmdReceived" and [this issue](https://github.com/genxium/DelayNoMore/issues/12). @@ -1801,7 +1439,7 @@ func (pR *Room) downsyncToAllPlayers(inputsBufferSnapshot *InputsBufferSnapshot) } } -func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRenderFrameId int32, unconfirmedMask uint64, toSendInputFrameDownsyncsSnapshot []*InputFrameDownsync, shouldForceResync bool) { +func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRenderFrameId int32, unconfirmedMask uint64, toSendInputFrameDownsyncsSnapshot []*pb.InputFrameDownsync, shouldForceResync bool) { /* [WARNING] This function MUST BE called while "pR.InputsBufferLock" is unlocked -- otherwise the network I/O blocking of "sendSafely" might cause significant lag for "markConfirmationIfApplicable & forceConfirmationIfApplicable"! @@ -1833,15 +1471,16 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender panic(fmt.Sprintf("Required refRenderFrameId=%v for (roomId=%v, renderFrameId=%v, playerId=%v, playerLastSentInputFrameId=%v) doesn't exist! InputsBuffer=%v, RenderFrameBuffer=%v", refRenderFrameId, pR.Id, pR.RenderFrameId, playerId, player.LastSentInputFrameId, pR.InputsBufferString(false), pR.RenderFrameBufferString())) } - refRenderFrame := tmp.(*RoomDownsyncFrame) - for _, player := range pR.PlayersArr { - refRenderFrame.Players[player.Id].ColliderRadius = player.ColliderRadius // hardcoded for now + 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 { refRenderFrame.ShouldForceResync = true } refRenderFrame.BackendUnconfirmedMask = unconfirmedMask - pR.sendSafely(refRenderFrame, toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false) + pR.sendSafely(toPbRoomDownsyncFrame(refRenderFrame), toSendInputFrameDownsyncsSnapshot, DOWNSYNC_MSG_ACT_FORCED_RESYNC, playerId, false) //Logger.Warn(fmt.Sprintf("Sent refRenderFrameId=%v & inputFrameIds [%d, %d), for roomId=%v, playerId=%d, playerJoinIndex=%d, renderFrameId=%d, curDynamicsRenderFrameId=%d, playerLastSentInputFrameId=%d: InputsBuffer=%v", refRenderFrameId, toSendInputFrameIdSt, toSendInputFrameIdEd, pR.Id, playerId, player.JoinIndex, pR.RenderFrameId, pR.CurDynamicsRenderFrameId, player.LastSentInputFrameId, pR.InputsBufferString(false))) 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)) @@ -1855,9 +1494,9 @@ func (pR *Room) downsyncToSinglePlayer(playerId int32, player *Player, refRender } } -func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDownsync { +func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*pb.InputFrameDownsync { // [WARNING] This function MUST BE called while "pR.InputsBufferLock" is locked! - cloned := make([]*InputFrameDownsync, 0, edFrameId-stFrameId) + cloned := make([]*pb.InputFrameDownsync, 0, edFrameId-stFrameId) prevFrameFound := false j := stFrameId for j < edFrameId { @@ -1871,9 +1510,9 @@ func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDowns } } prevFrameFound = true - foo := tmp.(*InputFrameDownsync) + foo := tmp.(*battle.InputFrameDownsync) - bar := &InputFrameDownsync{ + bar := &pb.InputFrameDownsync{ InputFrameId: foo.InputFrameId, InputList: make([]uint64, len(foo.InputList)), ConfirmedList: foo.ConfirmedList, @@ -1887,29 +1526,3 @@ func (pR *Room) cloneInputsBuffer(stFrameId, edFrameId int32) []*InputFrameDowns return cloned } - -func (pR *Room) calcHardPushbacksNorms(playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) []Vec2D { - ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks - collision := playerCollider.Check(0, 0) - if nil == collision { - return ret - } - for _, obj := range collision.Objects { - switch obj.Data.(type) { - case *Barrier: - barrierShape := obj.Shape.(*resolv.ConvexPolygon) - overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape) - if !overlapped { - continue - } - // ALWAY snap into hardPushbacks! - // [OverlapX, OverlapY] is the unit vector that points into the platform - pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY - ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY}) - pEffPushback.X += pushbackX - pEffPushback.Y += pushbackY - default: - } - } - return ret -} diff --git a/battle_srv/protos/room_downsync_frame.pb.go b/battle_srv/protos/room_downsync_frame.pb.go index cdbfed6..bab51dc 100644 --- a/battle_srv/protos/room_downsync_frame.pb.go +++ b/battle_srv/protos/room_downsync_frame.pb.go @@ -7,7 +7,7 @@ package protos import ( - sharedprotos "dnmshared/sharedprotos" + _ "dnmshared/sharedprotos" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -739,24 +739,26 @@ type MeleeBullet struct { unknownFields protoimpl.UnknownFields // for offender - BattleLocalId int32 `protobuf:"varint,1,opt,name=battleLocalId,proto3" json:"battleLocalId,omitempty"` - StartupFrames int32 `protobuf:"varint,2,opt,name=startupFrames,proto3" json:"startupFrames,omitempty"` - ActiveFrames int32 `protobuf:"varint,3,opt,name=activeFrames,proto3" json:"activeFrames,omitempty"` - RecoveryFrames int32 `protobuf:"varint,4,opt,name=recoveryFrames,proto3" json:"recoveryFrames,omitempty"` - RecoveryFramesOnBlock int32 `protobuf:"varint,5,opt,name=recoveryFramesOnBlock,proto3" json:"recoveryFramesOnBlock,omitempty"` - RecoveryFramesOnHit int32 `protobuf:"varint,6,opt,name=recoveryFramesOnHit,proto3" json:"recoveryFramesOnHit,omitempty"` - Moveforward *sharedprotos.Vec2D `protobuf:"bytes,7,opt,name=moveforward,proto3" json:"moveforward,omitempty"` - HitboxOffset float64 `protobuf:"fixed64,8,opt,name=hitboxOffset,proto3" json:"hitboxOffset,omitempty"` - HitboxSize *sharedprotos.Vec2D `protobuf:"bytes,9,opt,name=hitboxSize,proto3" json:"hitboxSize,omitempty"` - OriginatedRenderFrameId int32 `protobuf:"varint,10,opt,name=originatedRenderFrameId,proto3" json:"originatedRenderFrameId,omitempty"` + BattleLocalId int32 `protobuf:"varint,1,opt,name=battleLocalId,proto3" json:"battleLocalId,omitempty"` + StartupFrames int32 `protobuf:"varint,2,opt,name=startupFrames,proto3" json:"startupFrames,omitempty"` + ActiveFrames int32 `protobuf:"varint,3,opt,name=activeFrames,proto3" json:"activeFrames,omitempty"` + RecoveryFrames int32 `protobuf:"varint,4,opt,name=recoveryFrames,proto3" json:"recoveryFrames,omitempty"` + RecoveryFramesOnBlock int32 `protobuf:"varint,5,opt,name=recoveryFramesOnBlock,proto3" json:"recoveryFramesOnBlock,omitempty"` + RecoveryFramesOnHit int32 `protobuf:"varint,6,opt,name=recoveryFramesOnHit,proto3" json:"recoveryFramesOnHit,omitempty"` + HitboxOffset float64 `protobuf:"fixed64,7,opt,name=hitboxOffset,proto3" json:"hitboxOffset,omitempty"` + OriginatedRenderFrameId int32 `protobuf:"varint,8,opt,name=originatedRenderFrameId,proto3" json:"originatedRenderFrameId,omitempty"` // for defender - HitStunFrames int32 `protobuf:"varint,11,opt,name=hitStunFrames,proto3" json:"hitStunFrames,omitempty"` - BlockStunFrames int32 `protobuf:"varint,12,opt,name=blockStunFrames,proto3" json:"blockStunFrames,omitempty"` - Pushback float64 `protobuf:"fixed64,13,opt,name=pushback,proto3" json:"pushback,omitempty"` - ReleaseTriggerType int32 `protobuf:"varint,14,opt,name=releaseTriggerType,proto3" json:"releaseTriggerType,omitempty"` // 1: rising-edge, 2: falling-edge - Damage int32 `protobuf:"varint,15,opt,name=damage,proto3" json:"damage,omitempty"` - OffenderJoinIndex int32 `protobuf:"varint,16,opt,name=offenderJoinIndex,proto3" json:"offenderJoinIndex,omitempty"` - OffenderPlayerId int32 `protobuf:"varint,17,opt,name=offenderPlayerId,proto3" json:"offenderPlayerId,omitempty"` + HitStunFrames int32 `protobuf:"varint,9,opt,name=hitStunFrames,proto3" json:"hitStunFrames,omitempty"` + BlockStunFrames int32 `protobuf:"varint,10,opt,name=blockStunFrames,proto3" json:"blockStunFrames,omitempty"` + Pushback float64 `protobuf:"fixed64,11,opt,name=pushback,proto3" json:"pushback,omitempty"` + ReleaseTriggerType int32 `protobuf:"varint,12,opt,name=releaseTriggerType,proto3" json:"releaseTriggerType,omitempty"` // 1: rising-edge, 2: falling-edge + Damage int32 `protobuf:"varint,13,opt,name=damage,proto3" json:"damage,omitempty"` + OffenderJoinIndex int32 `protobuf:"varint,14,opt,name=offenderJoinIndex,proto3" json:"offenderJoinIndex,omitempty"` + OffenderPlayerId int32 `protobuf:"varint,15,opt,name=offenderPlayerId,proto3" json:"offenderPlayerId,omitempty"` + HitboxSizeX float64 `protobuf:"fixed64,16,opt,name=hitboxSizeX,proto3" json:"hitboxSizeX,omitempty"` + HitboxSizeY float64 `protobuf:"fixed64,17,opt,name=hitboxSizeY,proto3" json:"hitboxSizeY,omitempty"` + SelfMoveforwardX float64 `protobuf:"fixed64,18,opt,name=selfMoveforwardX,proto3" json:"selfMoveforwardX,omitempty"` + SelfMoveforwardY float64 `protobuf:"fixed64,19,opt,name=selfMoveforwardY,proto3" json:"selfMoveforwardY,omitempty"` } func (x *MeleeBullet) Reset() { @@ -833,13 +835,6 @@ func (x *MeleeBullet) GetRecoveryFramesOnHit() int32 { return 0 } -func (x *MeleeBullet) GetMoveforward() *sharedprotos.Vec2D { - if x != nil { - return x.Moveforward - } - return nil -} - func (x *MeleeBullet) GetHitboxOffset() float64 { if x != nil { return x.HitboxOffset @@ -847,13 +842,6 @@ func (x *MeleeBullet) GetHitboxOffset() float64 { return 0 } -func (x *MeleeBullet) GetHitboxSize() *sharedprotos.Vec2D { - if x != nil { - return x.HitboxSize - } - return nil -} - func (x *MeleeBullet) GetOriginatedRenderFrameId() int32 { if x != nil { return x.OriginatedRenderFrameId @@ -910,42 +898,69 @@ func (x *MeleeBullet) GetOffenderPlayerId() int32 { return 0 } +func (x *MeleeBullet) GetHitboxSizeX() float64 { + if x != nil { + return x.HitboxSizeX + } + return 0 +} + +func (x *MeleeBullet) GetHitboxSizeY() float64 { + if x != nil { + return x.HitboxSizeY + } + return 0 +} + +func (x *MeleeBullet) GetSelfMoveforwardX() float64 { + if x != nil { + return x.SelfMoveforwardX + } + return 0 +} + +func (x *MeleeBullet) GetSelfMoveforwardY() float64 { + if x != nil { + return x.SelfMoveforwardY + } + return 0 +} + type BattleColliderInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - StageName string `protobuf:"bytes,1,opt,name=stageName,proto3" json:"stageName,omitempty"` - StrToVec2DListMap map[string]*sharedprotos.Vec2DList `protobuf:"bytes,2,rep,name=strToVec2DListMap,proto3" json:"strToVec2DListMap,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - StrToPolygon2DListMap map[string]*sharedprotos.Polygon2DList `protobuf:"bytes,3,rep,name=strToPolygon2DListMap,proto3" json:"strToPolygon2DListMap,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - StageDiscreteW int32 `protobuf:"varint,4,opt,name=stageDiscreteW,proto3" json:"stageDiscreteW,omitempty"` - StageDiscreteH int32 `protobuf:"varint,5,opt,name=stageDiscreteH,proto3" json:"stageDiscreteH,omitempty"` - StageTileW int32 `protobuf:"varint,6,opt,name=stageTileW,proto3" json:"stageTileW,omitempty"` - StageTileH int32 `protobuf:"varint,7,opt,name=stageTileH,proto3" json:"stageTileH,omitempty"` - IntervalToPing int32 `protobuf:"varint,8,opt,name=intervalToPing,proto3" json:"intervalToPing,omitempty"` - WillKickIfInactiveFor int32 `protobuf:"varint,9,opt,name=willKickIfInactiveFor,proto3" json:"willKickIfInactiveFor,omitempty"` - BoundRoomId int32 `protobuf:"varint,10,opt,name=boundRoomId,proto3" json:"boundRoomId,omitempty"` - BattleDurationFrames int32 `protobuf:"varint,12,opt,name=battleDurationFrames,proto3" json:"battleDurationFrames,omitempty"` - BattleDurationNanos int64 `protobuf:"varint,13,opt,name=battleDurationNanos,proto3" json:"battleDurationNanos,omitempty"` - ServerFps int32 `protobuf:"varint,14,opt,name=serverFps,proto3" json:"serverFps,omitempty"` - InputDelayFrames int32 `protobuf:"varint,15,opt,name=inputDelayFrames,proto3" json:"inputDelayFrames,omitempty"` // in the count of render frames - InputScaleFrames uint32 `protobuf:"varint,16,opt,name=inputScaleFrames,proto3" json:"inputScaleFrames,omitempty"` // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) - NstDelayFrames int32 `protobuf:"varint,17,opt,name=nstDelayFrames,proto3" json:"nstDelayFrames,omitempty"` // 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" - InputFrameUpsyncDelayTolerance int32 `protobuf:"varint,18,opt,name=inputFrameUpsyncDelayTolerance,proto3" json:"inputFrameUpsyncDelayTolerance,omitempty"` - MaxChasingRenderFramesPerUpdate int32 `protobuf:"varint,19,opt,name=maxChasingRenderFramesPerUpdate,proto3" json:"maxChasingRenderFramesPerUpdate,omitempty"` - PlayerBattleState int32 `protobuf:"varint,20,opt,name=playerBattleState,proto3" json:"playerBattleState,omitempty"` - RollbackEstimatedDtMillis float64 `protobuf:"fixed64,21,opt,name=rollbackEstimatedDtMillis,proto3" json:"rollbackEstimatedDtMillis,omitempty"` - RollbackEstimatedDtNanos int64 `protobuf:"varint,22,opt,name=rollbackEstimatedDtNanos,proto3" json:"rollbackEstimatedDtNanos,omitempty"` - WorldToVirtualGridRatio float64 `protobuf:"fixed64,23,opt,name=worldToVirtualGridRatio,proto3" json:"worldToVirtualGridRatio,omitempty"` - VirtualGridToWorldRatio float64 `protobuf:"fixed64,24,opt,name=virtualGridToWorldRatio,proto3" json:"virtualGridToWorldRatio,omitempty"` - SpAtkLookupFrames int32 `protobuf:"varint,25,opt,name=spAtkLookupFrames,proto3" json:"spAtkLookupFrames,omitempty"` - RenderCacheSize int32 `protobuf:"varint,26,opt,name=renderCacheSize,proto3" json:"renderCacheSize,omitempty"` - MeleeSkillConfig map[int32]*MeleeBullet `protobuf:"bytes,27,rep,name=meleeSkillConfig,proto3" json:"meleeSkillConfig,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // skillId -> skill - SnapIntoPlatformOverlap float64 `protobuf:"fixed64,28,opt,name=snapIntoPlatformOverlap,proto3" json:"snapIntoPlatformOverlap,omitempty"` - SnapIntoPlatformThreshold float64 `protobuf:"fixed64,29,opt,name=snapIntoPlatformThreshold,proto3" json:"snapIntoPlatformThreshold,omitempty"` - JumpingInitVelY int32 `protobuf:"varint,30,opt,name=jumpingInitVelY,proto3" json:"jumpingInitVelY,omitempty"` - GravityX int32 `protobuf:"varint,31,opt,name=gravityX,proto3" json:"gravityX,omitempty"` - GravityY int32 `protobuf:"varint,32,opt,name=gravityY,proto3" json:"gravityY,omitempty"` + StageName string `protobuf:"bytes,1,opt,name=stageName,proto3" json:"stageName,omitempty"` + StageDiscreteW int32 `protobuf:"varint,2,opt,name=stageDiscreteW,proto3" json:"stageDiscreteW,omitempty"` + StageDiscreteH int32 `protobuf:"varint,3,opt,name=stageDiscreteH,proto3" json:"stageDiscreteH,omitempty"` + StageTileW int32 `protobuf:"varint,4,opt,name=stageTileW,proto3" json:"stageTileW,omitempty"` + StageTileH int32 `protobuf:"varint,5,opt,name=stageTileH,proto3" json:"stageTileH,omitempty"` + IntervalToPing int32 `protobuf:"varint,6,opt,name=intervalToPing,proto3" json:"intervalToPing,omitempty"` + WillKickIfInactiveFor int32 `protobuf:"varint,7,opt,name=willKickIfInactiveFor,proto3" json:"willKickIfInactiveFor,omitempty"` + BoundRoomId int32 `protobuf:"varint,8,opt,name=boundRoomId,proto3" json:"boundRoomId,omitempty"` + BattleDurationFrames int32 `protobuf:"varint,9,opt,name=battleDurationFrames,proto3" json:"battleDurationFrames,omitempty"` + BattleDurationNanos int64 `protobuf:"varint,10,opt,name=battleDurationNanos,proto3" json:"battleDurationNanos,omitempty"` + ServerFps int32 `protobuf:"varint,11,opt,name=serverFps,proto3" json:"serverFps,omitempty"` + InputDelayFrames int32 `protobuf:"varint,12,opt,name=inputDelayFrames,proto3" json:"inputDelayFrames,omitempty"` // in the count of render frames + InputScaleFrames uint32 `protobuf:"varint,13,opt,name=inputScaleFrames,proto3" json:"inputScaleFrames,omitempty"` // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) + NstDelayFrames int32 `protobuf:"varint,14,opt,name=nstDelayFrames,proto3" json:"nstDelayFrames,omitempty"` // 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" + InputFrameUpsyncDelayTolerance int32 `protobuf:"varint,15,opt,name=inputFrameUpsyncDelayTolerance,proto3" json:"inputFrameUpsyncDelayTolerance,omitempty"` + MaxChasingRenderFramesPerUpdate int32 `protobuf:"varint,16,opt,name=maxChasingRenderFramesPerUpdate,proto3" json:"maxChasingRenderFramesPerUpdate,omitempty"` + PlayerBattleState int32 `protobuf:"varint,17,opt,name=playerBattleState,proto3" json:"playerBattleState,omitempty"` + RollbackEstimatedDtMillis float64 `protobuf:"fixed64,18,opt,name=rollbackEstimatedDtMillis,proto3" json:"rollbackEstimatedDtMillis,omitempty"` + RollbackEstimatedDtNanos int64 `protobuf:"varint,19,opt,name=rollbackEstimatedDtNanos,proto3" json:"rollbackEstimatedDtNanos,omitempty"` + WorldToVirtualGridRatio float64 `protobuf:"fixed64,20,opt,name=worldToVirtualGridRatio,proto3" json:"worldToVirtualGridRatio,omitempty"` + VirtualGridToWorldRatio float64 `protobuf:"fixed64,21,opt,name=virtualGridToWorldRatio,proto3" json:"virtualGridToWorldRatio,omitempty"` + SpAtkLookupFrames int32 `protobuf:"varint,22,opt,name=spAtkLookupFrames,proto3" json:"spAtkLookupFrames,omitempty"` + RenderCacheSize int32 `protobuf:"varint,23,opt,name=renderCacheSize,proto3" json:"renderCacheSize,omitempty"` + SnapIntoPlatformOverlap float64 `protobuf:"fixed64,24,opt,name=snapIntoPlatformOverlap,proto3" json:"snapIntoPlatformOverlap,omitempty"` + SnapIntoPlatformThreshold float64 `protobuf:"fixed64,25,opt,name=snapIntoPlatformThreshold,proto3" json:"snapIntoPlatformThreshold,omitempty"` + JumpingInitVelY int32 `protobuf:"varint,26,opt,name=jumpingInitVelY,proto3" json:"jumpingInitVelY,omitempty"` + GravityX int32 `protobuf:"varint,27,opt,name=gravityX,proto3" json:"gravityX,omitempty"` + GravityY int32 `protobuf:"varint,28,opt,name=gravityY,proto3" json:"gravityY,omitempty"` + CollisionMinStep int32 `protobuf:"varint,29,opt,name=collisionMinStep,proto3" json:"collisionMinStep,omitempty"` + FrameDataLoggingEnabled bool `protobuf:"varint,999,opt,name=frameDataLoggingEnabled,proto3" json:"frameDataLoggingEnabled,omitempty"` } func (x *BattleColliderInfo) Reset() { @@ -987,20 +1002,6 @@ func (x *BattleColliderInfo) GetStageName() string { return "" } -func (x *BattleColliderInfo) GetStrToVec2DListMap() map[string]*sharedprotos.Vec2DList { - if x != nil { - return x.StrToVec2DListMap - } - return nil -} - -func (x *BattleColliderInfo) GetStrToPolygon2DListMap() map[string]*sharedprotos.Polygon2DList { - if x != nil { - return x.StrToPolygon2DListMap - } - return nil -} - func (x *BattleColliderInfo) GetStageDiscreteW() int32 { if x != nil { return x.StageDiscreteW @@ -1155,13 +1156,6 @@ func (x *BattleColliderInfo) GetRenderCacheSize() int32 { return 0 } -func (x *BattleColliderInfo) GetMeleeSkillConfig() map[int32]*MeleeBullet { - if x != nil { - return x.MeleeSkillConfig - } - return nil -} - func (x *BattleColliderInfo) GetSnapIntoPlatformOverlap() float64 { if x != nil { return x.SnapIntoPlatformOverlap @@ -1197,17 +1191,32 @@ func (x *BattleColliderInfo) GetGravityY() int32 { return 0 } +func (x *BattleColliderInfo) GetCollisionMinStep() int32 { + if x != nil { + return x.CollisionMinStep + } + return 0 +} + +func (x *BattleColliderInfo) GetFrameDataLoggingEnabled() bool { + if x != nil { + return x.FrameDataLoggingEnabled + } + return false +} + type RoomDownsyncFrame struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Players map[int32]*PlayerDownsync `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"` - MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // 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 - BackendUnconfirmedMask uint64 `protobuf:"varint,5,opt,name=backendUnconfirmedMask,proto3" json:"backendUnconfirmedMask,omitempty"` // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync - ShouldForceResync bool `protobuf:"varint,6,opt,name=shouldForceResync,proto3" json:"shouldForceResync,omitempty"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + PlayersArr []*PlayerDownsync `protobuf:"bytes,2,rep,name=playersArr,proto3" json:"playersArr,omitempty"` + CountdownNanos int64 `protobuf:"varint,3,opt,name=countdownNanos,proto3" json:"countdownNanos,omitempty"` + MeleeBullets []*MeleeBullet `protobuf:"bytes,4,rep,name=meleeBullets,proto3" json:"meleeBullets,omitempty"` // 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 + BackendUnconfirmedMask uint64 `protobuf:"varint,5,opt,name=backendUnconfirmedMask,proto3" json:"backendUnconfirmedMask,omitempty"` // Indexed by "joinIndex", same compression concern as stated in InputFrameDownsync + ShouldForceResync bool `protobuf:"varint,6,opt,name=shouldForceResync,proto3" json:"shouldForceResync,omitempty"` + PlayerOpPatternToSkillId map[int32]int32 `protobuf:"bytes,7,rep,name=playerOpPatternToSkillId,proto3" json:"playerOpPatternToSkillId,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } func (x *RoomDownsyncFrame) Reset() { @@ -1249,9 +1258,9 @@ func (x *RoomDownsyncFrame) GetId() int32 { return 0 } -func (x *RoomDownsyncFrame) GetPlayers() map[int32]*PlayerDownsync { +func (x *RoomDownsyncFrame) GetPlayersArr() []*PlayerDownsync { if x != nil { - return x.Players + return x.PlayersArr } return nil } @@ -1284,6 +1293,13 @@ func (x *RoomDownsyncFrame) GetShouldForceResync() bool { return false } +func (x *RoomDownsyncFrame) GetPlayerOpPatternToSkillId() map[int32]int32 { + if x != nil { + return x.PlayerOpPatternToSkillId + } + return nil +} + var File_room_downsync_frame_proto protoreflect.FileDescriptor var file_room_downsync_frame_proto_rawDesc = []byte{ @@ -1405,7 +1421,7 @@ var file_room_downsync_frame_proto_rawDesc = []byte{ 0x79, 0x6e, 0x63, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x79, - 0x6e, 0x63, 0x22, 0xe5, 0x05, 0x0a, 0x0b, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, + 0x6e, 0x63, 0x22, 0x95, 0x06, 0x0a, 0x0b, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, @@ -1422,181 +1438,160 @@ var file_room_downsync_frame_proto_rawDesc = []byte{ 0x12, 0x30, 0x0a, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x48, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x4f, 0x6e, 0x48, - 0x69, 0x74, 0x12, 0x35, 0x0a, 0x0b, 0x6d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x0b, 0x6d, 0x6f, - 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x68, 0x69, 0x74, - 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x33, 0x0a, - 0x0a, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, - 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x52, 0x0a, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, - 0x7a, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, - 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, - 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, - 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, - 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x12, 0x72, 0x65, 0x6c, 0x65, - 0x61, 0x73, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0e, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x72, 0x69, - 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x61, 0x6d, 0x61, - 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x64, 0x61, 0x6d, 0x61, 0x67, 0x65, - 0x12, 0x2c, 0x0a, 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x6f, 0x66, 0x66, - 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, - 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, - 0x49, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x22, 0xf2, 0x0e, 0x0a, 0x12, 0x42, + 0x69, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0c, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, + 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x38, 0x0a, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x17, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, + 0x74, 0x65, 0x64, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x64, + 0x12, 0x24, 0x0a, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x68, 0x69, 0x74, 0x53, 0x74, 0x75, 0x6e, + 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, + 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x74, 0x75, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x01, 0x52, 0x08, 0x70, 0x75, 0x73, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x12, + 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x61, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x64, 0x61, + 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x11, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6f, 0x66, + 0x66, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, + 0x0a, 0x0b, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x58, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x0b, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x58, + 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x59, 0x18, + 0x11, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x68, 0x69, 0x74, 0x62, 0x6f, 0x78, 0x53, 0x69, 0x7a, + 0x65, 0x59, 0x12, 0x2a, 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x66, 0x4d, 0x6f, 0x76, 0x65, 0x66, 0x6f, + 0x72, 0x77, 0x61, 0x72, 0x64, 0x58, 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x73, 0x65, + 0x6c, 0x66, 0x4d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x58, 0x12, 0x2a, + 0x0a, 0x10, 0x73, 0x65, 0x6c, 0x66, 0x4d, 0x6f, 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x59, 0x18, 0x13, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x73, 0x65, 0x6c, 0x66, 0x4d, 0x6f, + 0x76, 0x65, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x59, 0x22, 0x8d, 0x0b, 0x0a, 0x12, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x5f, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, - 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, - 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x11, 0x73, - 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, - 0x12, 0x6b, 0x0a, 0x15, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, - 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x35, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, - 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x53, 0x74, 0x72, 0x54, - 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, - 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x15, 0x73, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, - 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x12, 0x26, 0x0a, - 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x57, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, - 0x72, 0x65, 0x74, 0x65, 0x57, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, - 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x12, 0x1e, 0x0a, - 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x12, 0x1e, 0x0a, - 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x12, 0x26, 0x0a, - 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e, 0x67, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, - 0x6f, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b, 0x69, 0x63, - 0x6b, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b, 0x69, 0x63, 0x6b, 0x49, 0x66, - 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x32, 0x0a, - 0x14, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x62, 0x61, 0x74, - 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, - 0x73, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, - 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, - 0x6e, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, 0x70, 0x73, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, 0x70, - 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, 0x6e, 0x70, - 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x2a, 0x0a, - 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, - 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, - 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x6e, 0x73, 0x74, - 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x0e, 0x6e, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, - 0x73, 0x12, 0x46, 0x0a, 0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, - 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, - 0x6e, 0x63, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, - 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, - 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x1f, 0x6d, 0x61, 0x78, - 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x13, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x1f, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x61, 0x74, - 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, - 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x3c, 0x0a, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, - 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x15, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x12, - 0x3a, 0x0a, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, - 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, 0x69, 0x6d, - 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x38, 0x0a, 0x17, 0x77, - 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, - 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x17, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x77, 0x6f, - 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, - 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, - 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, - 0x18, 0x18, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, - 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, - 0x2c, 0x0a, 0x11, 0x73, 0x70, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x73, 0x70, 0x41, 0x74, - 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, - 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x5c, 0x0a, 0x10, 0x6d, 0x65, 0x6c, 0x65, 0x65, - 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1b, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x30, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x74, 0x74, 0x6c, - 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4d, 0x65, - 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x10, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x38, 0x0a, 0x17, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, - 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x70, - 0x18, 0x1c, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, - 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x70, 0x12, - 0x3c, 0x0a, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, - 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x1d, 0x20, 0x01, - 0x28, 0x01, 0x52, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, - 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x28, 0x0a, - 0x0f, 0x6a, 0x75, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x56, 0x65, 0x6c, 0x59, - 0x18, 0x1e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6a, 0x75, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x49, - 0x6e, 0x69, 0x74, 0x56, 0x65, 0x6c, 0x59, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, - 0x74, 0x79, 0x58, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, - 0x74, 0x79, 0x58, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x79, 0x59, 0x18, - 0x20, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x79, 0x59, 0x1a, - 0x5d, 0x0a, 0x16, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x68, 0x61, - 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x56, 0x65, 0x63, 0x32, 0x44, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x65, - 0x0a, 0x1a, 0x53, 0x74, 0x72, 0x54, 0x6f, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6f, 0x6c, - 0x79, 0x67, 0x6f, 0x6e, 0x32, 0x44, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x58, 0x0a, 0x15, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x53, 0x6b, - 0x69, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, - 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x80, 0x03, 0x0a, 0x11, 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, - 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, - 0x65, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, - 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, - 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x4d, - 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d, 0x65, 0x6c, 0x65, - 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62, 0x61, 0x63, 0x6b, - 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, - 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, - 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, 0x6b, - 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x68, 0x6f, - 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x79, 0x6e, 0x63, 0x1a, 0x52, - 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x44, - 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, + 0x57, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, + 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x57, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0e, 0x73, 0x74, 0x61, 0x67, 0x65, 0x44, 0x69, 0x73, 0x63, 0x72, 0x65, 0x74, 0x65, 0x48, 0x12, + 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x57, 0x12, + 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x67, 0x65, 0x54, 0x69, 0x6c, 0x65, 0x48, 0x12, + 0x26, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e, + 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x54, 0x6f, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b, + 0x69, 0x63, 0x6b, 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x77, 0x69, 0x6c, 0x6c, 0x4b, 0x69, 0x63, 0x6b, + 0x49, 0x66, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x46, 0x6f, 0x72, 0x12, 0x20, 0x0a, + 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0b, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, + 0x32, 0x0a, 0x14, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x62, + 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x12, 0x30, 0x0a, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x13, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x46, + 0x70, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x46, 0x70, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, + 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x69, + 0x6e, 0x70, 0x75, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, + 0x2a, 0x0a, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x53, 0x63, 0x61, 0x6c, 0x65, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x6e, + 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6e, 0x73, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x46, 0x72, 0x61, + 0x6d, 0x65, 0x73, 0x12, 0x46, 0x0a, 0x1e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, + 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65, + 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1e, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x55, 0x70, 0x73, 0x79, 0x6e, 0x63, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x1f, 0x6d, + 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, + 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x10, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x1f, 0x6d, 0x61, 0x78, 0x43, 0x68, 0x61, 0x73, 0x69, 0x6e, 0x67, + 0x52, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x50, 0x65, 0x72, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, + 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x11, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x3c, 0x0a, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, + 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, + 0x18, 0x12, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4d, 0x69, 0x6c, 0x6c, 0x69, + 0x73, 0x12, 0x3a, 0x0a, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x18, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x45, 0x73, 0x74, + 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x44, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x12, 0x38, 0x0a, + 0x17, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, + 0x72, 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x14, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, + 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x54, 0x6f, 0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x47, 0x72, + 0x69, 0x64, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75, + 0x61, 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74, + 0x69, 0x6f, 0x18, 0x15, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, + 0x6c, 0x47, 0x72, 0x69, 0x64, 0x54, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x52, 0x61, 0x74, 0x69, + 0x6f, 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x70, 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, + 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x16, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x73, 0x70, + 0x41, 0x74, 0x6b, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x12, + 0x28, 0x0a, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, + 0x7a, 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x73, 0x6e, 0x61, + 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, + 0x72, 0x6c, 0x61, 0x70, 0x18, 0x18, 0x20, 0x01, 0x28, 0x01, 0x52, 0x17, 0x73, 0x6e, 0x61, 0x70, + 0x49, 0x6e, 0x74, 0x6f, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x4f, 0x76, 0x65, 0x72, + 0x6c, 0x61, 0x70, 0x12, 0x3c, 0x0a, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, 0x50, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, + 0x18, 0x19, 0x20, 0x01, 0x28, 0x01, 0x52, 0x19, 0x73, 0x6e, 0x61, 0x70, 0x49, 0x6e, 0x74, 0x6f, + 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x6a, 0x75, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x69, 0x74, + 0x56, 0x65, 0x6c, 0x59, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6a, 0x75, 0x6d, 0x70, + 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x56, 0x65, 0x6c, 0x59, 0x12, 0x1a, 0x0a, 0x08, 0x67, + 0x72, 0x61, 0x76, 0x69, 0x74, 0x79, 0x58, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, + 0x72, 0x61, 0x76, 0x69, 0x74, 0x79, 0x58, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, + 0x74, 0x79, 0x59, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x67, 0x72, 0x61, 0x76, 0x69, + 0x74, 0x79, 0x59, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x4d, 0x69, 0x6e, 0x53, 0x74, 0x65, 0x70, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x63, + 0x6f, 0x6c, 0x6c, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e, 0x53, 0x74, 0x65, 0x70, 0x12, + 0x39, 0x0a, 0x17, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x6f, 0x67, 0x67, + 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0xe7, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x17, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x6f, 0x67, 0x67, + 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xe4, 0x03, 0x0a, 0x11, 0x52, + 0x6f, 0x6f, 0x6d, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x36, 0x0a, 0x0a, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x41, 0x72, 0x72, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x50, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x44, 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x52, 0x0a, 0x70, 0x6c, + 0x61, 0x79, 0x65, 0x72, 0x73, 0x41, 0x72, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0e, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, + 0x12, 0x37, 0x0a, 0x0c, 0x6d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, + 0x4d, 0x65, 0x6c, 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x52, 0x0c, 0x6d, 0x65, 0x6c, + 0x65, 0x65, 0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x62, 0x61, 0x63, + 0x6b, 0x65, 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, + 0x61, 0x73, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x62, 0x61, 0x63, 0x6b, 0x65, + 0x6e, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x4d, 0x61, 0x73, + 0x6b, 0x12, 0x2c, 0x0a, 0x11, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x68, + 0x6f, 0x75, 0x6c, 0x64, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x79, 0x6e, 0x63, 0x12, + 0x73, 0x0a, 0x18, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, 0x50, 0x61, 0x74, 0x74, 0x65, + 0x72, 0x6e, 0x54, 0x6f, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x49, 0x64, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x37, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x44, + 0x6f, 0x77, 0x6e, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x2e, 0x50, 0x6c, 0x61, + 0x79, 0x65, 0x72, 0x4f, 0x70, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x6f, 0x53, 0x6b, + 0x69, 0x6c, 0x6c, 0x49, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x18, 0x70, 0x6c, 0x61, 0x79, + 0x65, 0x72, 0x4f, 0x70, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x6f, 0x53, 0x6b, 0x69, + 0x6c, 0x6c, 0x49, 0x64, 0x1a, 0x4b, 0x0a, 0x1d, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x4f, 0x70, + 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x54, 0x6f, 0x53, 0x6b, 0x69, 0x6c, 0x6c, 0x49, 0x64, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x42, 0x13, 0x5a, 0x11, 0x62, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x73, 0x72, 0x76, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1611,26 +1606,20 @@ func file_room_downsync_frame_proto_rawDescGZIP() []byte { return file_room_downsync_frame_proto_rawDescData } -var file_room_downsync_frame_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_room_downsync_frame_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_room_downsync_frame_proto_goTypes = []interface{}{ - (*PlayerDownsync)(nil), // 0: protos.PlayerDownsync - (*InputFrameDecoded)(nil), // 1: protos.InputFrameDecoded - (*InputFrameUpsync)(nil), // 2: protos.InputFrameUpsync - (*InputFrameDownsync)(nil), // 3: protos.InputFrameDownsync - (*HeartbeatUpsync)(nil), // 4: protos.HeartbeatUpsync - (*WsReq)(nil), // 5: protos.WsReq - (*WsResp)(nil), // 6: protos.WsResp - (*InputsBufferSnapshot)(nil), // 7: protos.InputsBufferSnapshot - (*MeleeBullet)(nil), // 8: protos.MeleeBullet - (*BattleColliderInfo)(nil), // 9: protos.BattleColliderInfo - (*RoomDownsyncFrame)(nil), // 10: protos.RoomDownsyncFrame - nil, // 11: protos.BattleColliderInfo.StrToVec2DListMapEntry - nil, // 12: protos.BattleColliderInfo.StrToPolygon2DListMapEntry - nil, // 13: protos.BattleColliderInfo.MeleeSkillConfigEntry - nil, // 14: protos.RoomDownsyncFrame.PlayersEntry - (*sharedprotos.Vec2D)(nil), // 15: sharedprotos.Vec2D - (*sharedprotos.Vec2DList)(nil), // 16: sharedprotos.Vec2DList - (*sharedprotos.Polygon2DList)(nil), // 17: sharedprotos.Polygon2DList + (*PlayerDownsync)(nil), // 0: protos.PlayerDownsync + (*InputFrameDecoded)(nil), // 1: protos.InputFrameDecoded + (*InputFrameUpsync)(nil), // 2: protos.InputFrameUpsync + (*InputFrameDownsync)(nil), // 3: protos.InputFrameDownsync + (*HeartbeatUpsync)(nil), // 4: protos.HeartbeatUpsync + (*WsReq)(nil), // 5: protos.WsReq + (*WsResp)(nil), // 6: protos.WsResp + (*InputsBufferSnapshot)(nil), // 7: protos.InputsBufferSnapshot + (*MeleeBullet)(nil), // 8: protos.MeleeBullet + (*BattleColliderInfo)(nil), // 9: protos.BattleColliderInfo + (*RoomDownsyncFrame)(nil), // 10: protos.RoomDownsyncFrame + nil, // 11: protos.RoomDownsyncFrame.PlayerOpPatternToSkillIdEntry } var file_room_downsync_frame_proto_depIdxs = []int32{ 2, // 0: protos.WsReq.inputFrameUpsyncBatch:type_name -> protos.InputFrameUpsync @@ -1639,22 +1628,14 @@ var file_room_downsync_frame_proto_depIdxs = []int32{ 3, // 3: protos.WsResp.inputFrameDownsyncBatch:type_name -> protos.InputFrameDownsync 9, // 4: protos.WsResp.bciFrame:type_name -> protos.BattleColliderInfo 3, // 5: protos.InputsBufferSnapshot.toSendInputFrameDownsyncs:type_name -> protos.InputFrameDownsync - 15, // 6: protos.MeleeBullet.moveforward:type_name -> sharedprotos.Vec2D - 15, // 7: protos.MeleeBullet.hitboxSize:type_name -> sharedprotos.Vec2D - 11, // 8: protos.BattleColliderInfo.strToVec2DListMap:type_name -> protos.BattleColliderInfo.StrToVec2DListMapEntry - 12, // 9: protos.BattleColliderInfo.strToPolygon2DListMap:type_name -> protos.BattleColliderInfo.StrToPolygon2DListMapEntry - 13, // 10: protos.BattleColliderInfo.meleeSkillConfig:type_name -> protos.BattleColliderInfo.MeleeSkillConfigEntry - 14, // 11: protos.RoomDownsyncFrame.players:type_name -> protos.RoomDownsyncFrame.PlayersEntry - 8, // 12: protos.RoomDownsyncFrame.meleeBullets:type_name -> protos.MeleeBullet - 16, // 13: protos.BattleColliderInfo.StrToVec2DListMapEntry.value:type_name -> sharedprotos.Vec2DList - 17, // 14: protos.BattleColliderInfo.StrToPolygon2DListMapEntry.value:type_name -> sharedprotos.Polygon2DList - 8, // 15: protos.BattleColliderInfo.MeleeSkillConfigEntry.value:type_name -> protos.MeleeBullet - 0, // 16: protos.RoomDownsyncFrame.PlayersEntry.value:type_name -> protos.PlayerDownsync - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 0, // 6: protos.RoomDownsyncFrame.playersArr:type_name -> protos.PlayerDownsync + 8, // 7: protos.RoomDownsyncFrame.meleeBullets:type_name -> protos.MeleeBullet + 11, // 8: protos.RoomDownsyncFrame.playerOpPatternToSkillId:type_name -> protos.RoomDownsyncFrame.PlayerOpPatternToSkillIdEntry + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_room_downsync_frame_proto_init() } @@ -1802,7 +1783,7 @@ func file_room_downsync_frame_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_room_downsync_frame_proto_rawDesc, NumEnums: 0, - NumMessages: 15, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/battle_srv/ws/serve.go b/battle_srv/ws/serve.go index 8299428..b6f6215 100644 --- a/battle_srv/ws/serve.go +++ b/battle_srv/ws/serve.go @@ -240,14 +240,12 @@ func Serve(c *gin.Context) { // Construct "battleColliderInfo" to downsync bciFrame := &pb.BattleColliderInfo{ - BoundRoomId: pRoom.Id, - StageName: pRoom.StageName, - StrToVec2DListMap: pRoom.StrToVec2DListMap, - StrToPolygon2DListMap: pRoom.StrToPolygon2DListMap, - StageDiscreteW: pRoom.StageDiscreteW, - StageDiscreteH: pRoom.StageDiscreteH, - StageTileW: pRoom.StageTileW, - StageTileH: pRoom.StageTileH, + BoundRoomId: pRoom.Id, + StageName: pRoom.StageName, + StageDiscreteW: pRoom.StageDiscreteW, + StageDiscreteH: pRoom.StageDiscreteH, + StageTileW: pRoom.StageTileW, + StageTileH: pRoom.StageTileH, IntervalToPing: int32(Constants.Ws.IntervalToPing), WillKickIfInactiveFor: int32(Constants.Ws.WillKickIfInactiveFor), @@ -267,12 +265,14 @@ func Serve(c *gin.Context) { SpAtkLookupFrames: pRoom.SpAtkLookupFrames, RenderCacheSize: pRoom.RenderCacheSize, - MeleeSkillConfig: pRoom.MeleeSkillConfig, SnapIntoPlatformOverlap: pRoom.SnapIntoPlatformOverlap, SnapIntoPlatformThreshold: pRoom.SnapIntoPlatformThreshold, JumpingInitVelY: pRoom.JumpingInitVelY, GravityX: pRoom.GravityX, GravityY: pRoom.GravityY, + CollisionMinStep: pRoom.CollisionMinStep, + + FrameDataLoggingEnabled: pRoom.FrameDataLoggingEnabled, } resp := &pb.WsResp{ diff --git a/collider_visualizer/common.go b/collider_visualizer/common.go index feab531..c054be3 100644 --- a/collider_visualizer/common.go +++ b/collider_visualizer/common.go @@ -2,8 +2,8 @@ package main import ( "github.com/hajimehoshi/ebiten/v2" - "github.com/solarlune/resolv" "image/color" + "resolv" ) var ( diff --git a/collider_visualizer/go.mod b/collider_visualizer/go.mod index d9deb8b..2bb4dcf 100644 --- a/collider_visualizer/go.mod +++ b/collider_visualizer/go.mod @@ -1,30 +1,44 @@ module viscol -go 1.19 +go 1.18 require ( + jsexport v0.0.0 dnmshared v0.0.0 - battle_srv v0.0.0 + resolv v0.0.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/hajimehoshi/ebiten/v2 v2.4.7 - github.com/solarlune/resolv v0.5.1 golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 ) require ( github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad // indirect github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jezek/xgb v1.0.1 // indirect - github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0 // indirect + github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 // indirect + github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c // indirect + github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/cobra v1.2.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect + golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect + golang.org/x/tools v0.1.12 // indirect google.golang.org/protobuf v1.28.1 // indirect ) -replace dnmshared => ../dnmshared -replace battle_srv => ../battle_srv +replace ( + dnmshared => ../dnmshared + jsexport => ../jsexport + resolv => ../resolv_tailored +) diff --git a/collider_visualizer/go.sum b/collider_visualizer/go.sum index 376124f..a94bc6d 100644 --- a/collider_visualizer/go.sum +++ b/collider_visualizer/go.sum @@ -1,14 +1,153 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744 h1:A8UnJ/5OKzki4HBDwoRQz7I6sxKsokpMXcGh+fUxpfc= github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad h1:kX51IjbsJPCvzV9jUoVQG9GEUqIq5hjfYzXTqQ52Rh8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gopherjs/gopherjs v1.18.0-beta1 h1:IbykhVEq4SAjwyBRuNHl0aOO6w6IqgL3RUdMhoBo4mY= +github.com/gopherjs/gopherjs v1.18.0-beta1/go.mod h1:6UY8PXRnu51MqjYCCY4toG0S5GeH5uVJ3qDxIsa+kqo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hajimehoshi/bitmapfont/v2 v2.2.1 h1:y7zcy02/UgO24IL3COqYtrRZzhRucNBtmCo/SNU648k= github.com/hajimehoshi/bitmapfont/v2 v2.2.1/go.mod h1:wjrYAy8vKgj9JsFgnYAOK346/uvE22TlmqouzdnYIs0= github.com/hajimehoshi/ebiten/v2 v2.4.7 h1:XuvB7R0Rbw/O7g6vNU8gqr5b9e7MNhhAONMSsyreLDI= @@ -18,63 +157,292 @@ github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8= github.com/jezek/xgb v1.0.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= github.com/jfreymuth/oggvorbis v1.0.4/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0 h1:v8lWpj5957KtDMKu+xQtlu6G3ZoZR6Tn9bsfZCRG5Xw= github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c h1:bY6ktFuJkt+ZXkX0RChQch2FtHpWQLVS8Qo1YasiIVk= +github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/solarlune/resolv v0.5.1 h1:Ul6PAs/zaxiMUOEYz1Z6VeUj5k3CDcWMvSh+kivybDY= github.com/solarlune/resolv v0.5.1/go.mod h1:HjM2f/0NoVjVdZsi26GtugX5aFbA62COEFEXkOhveRw= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -83,20 +451,200 @@ golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5g golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/collider_visualizer/main.go b/collider_visualizer/main.go index d972b7e..fef8009 100644 --- a/collider_visualizer/main.go +++ b/collider_visualizer/main.go @@ -11,13 +11,13 @@ import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/text" - "github.com/solarlune/resolv" "golang.org/x/image/font" "encoding/xml" "io/ioutil" "os" "path/filepath" + "resolv" . "dnmshared" ) diff --git a/collider_visualizer/worldColliderDisplay.go b/collider_visualizer/worldColliderDisplay.go index 11517c6..bd526e0 100644 --- a/collider_visualizer/worldColliderDisplay.go +++ b/collider_visualizer/worldColliderDisplay.go @@ -1,14 +1,13 @@ package main import ( - . "battle_srv/protos" . "dnmshared" - . "dnmshared/sharedprotos" "fmt" "github.com/hajimehoshi/ebiten/v2" - "github.com/solarlune/resolv" "go.uber.org/zap" "image/color" + . "jsexport/battle" + "resolv" ) type WorldColliderDisplay struct { @@ -39,21 +38,21 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi playerDefaultSpeed := 1 * worldToVirtualGridRatio minStep := (int(float64(playerDefaultSpeed)*virtualGridToWorldRatio) << 3) playerColliderRadius := float64(12) - playerColliders := make([]*resolv.Object, len(playerPosList.Eles)) + playerColliders := make([]*resolv.Object, len(playerPosList)) snapIntoPlatformOverlap := float64(0.1) space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) topPadding, bottomPadding, leftPadding, rightPadding := snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap - for i, playerPos := range playerPosList.Eles { + for i, playerPos := range playerPosList { colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4 - playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" + playerCollider := GenerateRectCollider(playerPos.X, playerPos.Y, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, fmt.Sprintf("Player%d", i+1)) // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset" Logger.Info(fmt.Sprintf("Player Collider#%d: player world pos=(%.2f, %.2f), shape=%v", i, playerPos.X, playerPos.Y, ConvexPolygonStr(playerCollider.Shape.(*resolv.ConvexPolygon)))) playerColliders[i] = playerCollider space.Add(playerCollider) } barrierLocalId := 0 - for _, barrierUnaligned := range barrierList.Eles { - barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier") + for _, barrierUnaligned := range barrierList { + barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, nil, "Barrier") Logger.Debug(fmt.Sprintf("Added barrier: shape=%v", ConvexPolygonStr(barrierCollider.Shape.(*resolv.ConvexPolygon)))) space.Add(barrierCollider) barrierLocalId++ @@ -64,16 +63,14 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi moveToCollide := true if moveToCollide { effPushback := Vec2D{X: float64(0), Y: float64(0)} - toTestPlayerCollider := playerColliders[0] colliderWidth, colliderHeight := playerColliderRadius*2, playerColliderRadius*4 - newVx, newVy := int32(-189000), int32(-497000) - toTestPlayerCollider.X, toTestPlayerCollider.Y = VirtualGridToPolygonColliderBLPos(newVx, newVy, colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) - playerColliders[1].X, playerColliders[1].Y = VirtualGridToPolygonColliderBLPos(int32(-165000), int32(-504000), colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) - playerColliders[1].Update() + playerColliders[0].X, playerColliders[0].Y = VirtualGridToPolygonColliderBLPos(int32(-139000), int32(-474500), colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) + playerColliders[0].Update() - Logger.Info(fmt.Sprintf("Checking collision for playerShape=%v", ConvexPolygonStr(toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)))) + playerColliders[1].X, playerColliders[1].Y = VirtualGridToPolygonColliderBLPos(int32(-163000), int32(-520000), colliderWidth, colliderHeight, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, virtualGridToWorldRatio) + playerColliders[1].Update() - toTestPlayerCollider.Update() + toTestPlayerCollider := playerColliders[1] if collision := toTestPlayerCollider.Check(0, 0); collision != nil { playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon) for _, obj := range collision.Objects { @@ -87,9 +84,9 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi Logger.Warn(fmt.Sprintf("Collided BUT not overlapped: a=%v, b=%v, overlapResult=%v", ConvexPolygonStr(playerShape), ConvexPolygonStr(bShape), overlapResult)) } } - toTestPlayerCollider.X -= effPushback.X - toTestPlayerCollider.Y -= effPushback.Y - toTestPlayerCollider.Update() + //toTestPlayerCollider.X -= effPushback.X + //toTestPlayerCollider.Y -= effPushback.Y + //toTestPlayerCollider.Update() Logger.Info(fmt.Sprintf("effPushback={%v, %v}", effPushback.X, effPushback.Y)) } } @@ -100,15 +97,11 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi RecoveryFrames: int32(61), RecoveryFramesOnBlock: int32(61), RecoveryFramesOnHit: int32(61), - Moveforward: &Vec2D{ - X: 0, - Y: 0, - }, - HitboxOffset: float64(24.0), - HitboxSize: &Vec2D{ - X: float64(45.0), - Y: float64(32.0), - }, + SelfMoveforwardX: 0, + SelfMoveforwardY: 0, + HitboxOffset: float64(24.0), + HitboxSizeX: float64(45.0), + HitboxSizeY: float64(32.0), // for defender HitStunFrames: int32(18), @@ -120,9 +113,9 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi bulletLeftToRight := false if bulletLeftToRight { xfac := float64(1.0) - offenderWx, offenderWy := playerPosList.Eles[0].X, playerPosList.Eles[0].Y + offenderWx, offenderWy := playerPosList[0].X, playerPosList[0].Y bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, "MeleeBullet") + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, "MeleeBullet") space.Add(newBulletCollider) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) Logger.Warn(fmt.Sprintf("bullet ->: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) @@ -142,10 +135,10 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi bulletRightToLeft := false if bulletRightToLeft { xfac := float64(-1.0) - offenderWx, offenderWy := playerPosList.Eles[1].X, playerPosList.Eles[1].Y + offenderWx, offenderWy := playerPosList[1].X, playerPosList[1].Y bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy - newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSize.X, meleeBullet.HitboxSize.Y, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, "MeleeBullet") + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, nil, "MeleeBullet") space.Add(newBulletCollider) bulletShape := newBulletCollider.Shape.(*resolv.ConvexPolygon) Logger.Warn(fmt.Sprintf("bullet <-: Added bullet collider to space: a=%v", ConvexPolygonStr(bulletShape))) @@ -172,11 +165,14 @@ func (world *WorldColliderDisplay) Update() { func (world *WorldColliderDisplay) Draw(screen *ebiten.Image) { for _, o := range world.Space.Objects() { - if o.HasTags("Player") { - drawColor := color.RGBA{0, 255, 0, 255} + if o.HasTags("Player1") { + drawColor := color.RGBA{255, 0, 0, 255} + DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor) + } else if o.HasTags("Player2") { + drawColor := color.RGBA{0, 0, 255, 255} DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor) } else if o.HasTags("MeleeBullet") { - drawColor := color.RGBA{0, 0, 255, 255} + drawColor := color.RGBA{78, 255, 112, 255} DrawPolygon(screen, o.Shape.(*resolv.ConvexPolygon), drawColor) } else { drawColor := color.RGBA{60, 60, 60, 255} diff --git a/dnmshared/geometry.go b/dnmshared/geometry.go deleted file mode 100644 index 1b8e4c8..0000000 --- a/dnmshared/geometry.go +++ /dev/null @@ -1,50 +0,0 @@ -package dnmshared - -import ( - . "dnmshared/sharedprotos" - "math" -) - -func NormVec2D(dx, dy float64) Vec2D { - return Vec2D{X: dy, Y: -dx} -} - -func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D { - // Transform again to put "anchor" at the "bottom-left point (w.r.t. world space)" of the bounding box for "resolv" - boundingBoxBL := &Vec2D{ - X: math.MaxFloat64, - Y: math.MaxFloat64, - } - for _, p := range input.Points { - if p.X < boundingBoxBL.X { - boundingBoxBL.X = p.X - } - if p.Y < boundingBoxBL.Y { - boundingBoxBL.Y = p.Y - } - } - - // Now "input.Anchor" should move to "input.Anchor+boundingBoxBL", thus "boundingBoxBL" is also the value of the negative diff for all "input.Points" - output := &Polygon2D{ - Anchor: &Vec2D{ - X: input.Anchor.X + boundingBoxBL.X, - Y: input.Anchor.Y + boundingBoxBL.Y, - }, - Points: make([]*Vec2D, len(input.Points)), - } - - for i, p := range input.Points { - output.Points[i] = &Vec2D{ - X: p.X - boundingBoxBL.X, - Y: p.Y - boundingBoxBL.Y, - } - } - - return output -} - -func Distance(pt1 *Vec2D, pt2 *Vec2D) float64 { - dx := pt1.X - pt2.X - dy := pt1.Y - pt2.Y - return math.Sqrt(dx*dx + dy*dy) -} diff --git a/dnmshared/go.mod b/dnmshared/go.mod index 919ac63..dac3d35 100644 --- a/dnmshared/go.mod +++ b/dnmshared/go.mod @@ -1,3 +1,11 @@ module dnmshared -go 1.19 +go 1.18 + +require ( + resolv v0.0.0 +) + +replace ( + resolv => ../resolv_tailored +) diff --git a/dnmshared/resolv_helper.go b/dnmshared/resolv_helper.go index cb91238..0c6a4c3 100644 --- a/dnmshared/resolv_helper.go +++ b/dnmshared/resolv_helper.go @@ -1,14 +1,16 @@ package dnmshared import ( - . "dnmshared/sharedprotos" "fmt" - "github.com/kvartborg/vector" - "github.com/solarlune/resolv" - "math" + . "jsexport/battle" + "resolv" "strings" ) +func NormVec2D(dx, dy float64) Vec2D { + return Vec2D{X: dy, Y: -dx} +} + func ConvexPolygonStr(body *resolv.ConvexPolygon) string { var s []string = make([]string, len(body.Points)) for i, p := range body.Points { @@ -21,249 +23,3 @@ func ConvexPolygonStr(body *resolv.ConvexPolygon) string { func RectCenterStr(body *resolv.Object, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64) string { return fmt.Sprintf("{%.2f, %.2f}", body.X+leftPadding+halfBoundingW-spaceOffsetX, body.Y+bottomPadding+halfBoundingH-spaceOffsetY) } - -func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object { - blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY) - return generateRectColliderInCollisionSpace(blX, blY, leftPadding+w+rightPadding, bottomPadding+h+topPadding, tag) -} - -func generateRectColliderInCollisionSpace(blX, blY, w, h float64, tag string) *resolv.Object { - collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details - shape := resolv.NewRectangle(0, 0, w, h) - collider.SetShape(shape) - return collider -} - -func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object { - aligned := AlignPolygon2DToBoundingBox(unalignedSrc) - var w, h float64 = 0, 0 - - shape := resolv.NewConvexPolygon() - for i, pi := range aligned.Points { - for j, pj := range aligned.Points { - if i == j { - continue - } - if math.Abs(pj.X-pi.X) > w { - w = math.Abs(pj.X - pi.X) - } - if math.Abs(pj.Y-pi.Y) > h { - h = math.Abs(pj.Y - pi.Y) - } - } - } - - for i := 0; i < len(aligned.Points); i++ { - p := aligned.Points[i] - shape.AddPoints(p.X, p.Y) - } - - collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag) - collider.SetShape(shape) - - return collider -} - -func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) { - origX, origY := playerShape.Position() - defer func() { - playerShape.SetPosition(origX, origY) - }() - playerShape.SetPosition(origX+oldDx, origY+oldDy) - - overlapResult := &SatResult{ - Overlap: 0, - OverlapX: 0, - OverlapY: 0, - AContainedInB: true, - BContainedInA: true, - Axis: vector.Vector{0, 0}, - } - if overlapped := isPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped { - pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY - return true, pushbackX, pushbackY, overlapResult - } else { - return false, 0, 0, overlapResult - } -} - -type SatResult struct { - Overlap float64 - OverlapX float64 - OverlapY float64 - AContainedInB bool - BContainedInA bool - Axis vector.Vector -} - -func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { - aCnt, bCnt := len(a.Points), len(b.Points) - // Single point case - if 1 == aCnt && 1 == bCnt { - if nil != result { - result.Overlap = 0 - } - return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1] - } - - //Logger.Info(fmt.Sprintf("Checking collision between a=%v, b=%v", ConvexPolygonStr(a), ConvexPolygonStr(b))) - if 1 < aCnt { - for _, axis := range a.SATAxes() { - if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { - return false - } - } - } - - if 1 < bCnt { - for _, axis := range b.SATAxes() { - if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { - return false - } - } - } - //Logger.Info(fmt.Sprintf("a=%v and b=%v are overlapped", ConvexPolygonStr(a), ConvexPolygonStr(b))) - - return true -} - -func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, result *SatResult) bool { - /* - [WARNING] This function is deliberately made private, it shouldn't be used alone (i.e. not along the norms of a polygon), otherwise the pushbacks calculated would be meaningless. - - Consider the following example - a: { - anchor: [1337.19 1696.74] - points: [[0 0] [24 0] [24 24] [0 24]] - }, - b: { - anchor: [1277.72 1570.56] - points: [[642.57 319.16] [0 319.16] [5.73 0] [643.75 0.90]] - } - - e = (-2.98, 1.49).Unit() - */ - - //Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#1", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1])) - var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64 - for _, p := range a.Points { - dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1] - - if aStart > dot { - aStart = dot - } - - if aEnd < dot { - aEnd = dot - } - } - - for _, p := range b.Points { - dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1] - - if bStart > dot { - bStart = dot - } - - if bEnd < dot { - bEnd = dot - } - } - - if aStart > bEnd || aEnd < bStart { - // Separated by unit vector "e" - return true - } - - if nil != result { - overlap := float64(0) - - if aStart < bStart { - result.AContainedInB = false - - if aEnd < bEnd { - overlap = aEnd - bStart - result.BContainedInA = false - } else { - option1 := aEnd - bStart - option2 := bEnd - aStart - if option1 < option2 { - overlap = option1 - } else { - overlap = -option2 - } - } - } else { - result.BContainedInA = false - - if aEnd > bEnd { - overlap = aStart - bEnd - result.AContainedInB = false - } else { - option1 := aEnd - bStart - option2 := bEnd - aStart - if option1 < option2 { - overlap = option1 - } else { - overlap = -option2 - } - } - } - - currentOverlap := result.Overlap - absoluteOverlap := overlap - if overlap < 0 { - absoluteOverlap = -overlap - } - - if (0 == result.Axis[0] && 0 == result.Axis[1]) || currentOverlap > absoluteOverlap { - var sign float64 = 1 - if overlap < 0 { - sign = -1 - } - - result.Overlap = absoluteOverlap - result.OverlapX = e[0] * sign - result.OverlapY = e[1] * sign - } - - result.Axis = e - //Logger.Info(fmt.Sprintf("Checking separation between a=%v, b=%v along axis e={%.3f, %.3f}#2: aStart=%.3f, aEnd=%.3f, bStart=%.3f, bEnd=%.3f, overlap=%.3f, currentOverlap=%.3f, absoluteOverlap=%.3f, result=%v", ConvexPolygonStr(a), ConvexPolygonStr(b), e[0], e[1], aStart, aEnd, bStart, bEnd, overlap, currentOverlap, absoluteOverlap, result)) - } - - // the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated - return false -} - -func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) { - // [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. - var virtualGridX int32 = int32(math.Round(wx * worldToVirtualGridRatio)) - var virtualGridY int32 = int32(math.Round(wy * worldToVirtualGridRatio)) - return virtualGridX, virtualGridY -} - -func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) { - // No loss of precision - var wx float64 = float64(vx) * virtualGridToWorldRatio - var wy float64 = float64(vy) * virtualGridToWorldRatio - return wx, wy -} - -func WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { - return wx - halfBoundingW - leftPadding + collisionSpaceOffsetX, wy - halfBoundingH - bottomPadding + collisionSpaceOffsetY -} - -func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { - 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) { - wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) - return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) -} - -func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { - wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) - return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) -} diff --git a/dnmshared/tmx_parser.go b/dnmshared/tmx_parser.go index e67e3d7..c5d7a48 100644 --- a/dnmshared/tmx_parser.go +++ b/dnmshared/tmx_parser.go @@ -3,13 +3,13 @@ package dnmshared import ( "bytes" "compress/zlib" - . "dnmshared/sharedprotos" "encoding/base64" "encoding/xml" "errors" "fmt" "go.uber.org/zap" "io/ioutil" + . "jsexport/battle" "math" "strconv" "strings" @@ -175,8 +175,8 @@ func (l *TmxLayer) decodeBase64() ([]uint32, error) { return gids, nil } -type StrToVec2DListMap map[string]*Vec2DList -type StrToPolygon2DListMap map[string]*Polygon2DList +type StrToVec2DListMap map[string](*[]*Vec2D) +type StrToPolygon2DListMap map[string](*[]*Polygon2D) func tmxPolylineToPolygon2D(pTmxMapIns *TmxMap, singleObjInTmxFile *TmxOrTsxObject, targetPolyline *TmxOrTsxPolyline) (*Polygon2D, error) { if nil == targetPolyline { @@ -321,21 +321,18 @@ func DeserializeTsxToColliderDict(pTmxMapIns *TmxMap, byteArrOfTsxFile []byte, f theStrToPolygon2DListMap = gidBoundariesMap[globalGid] } - var pThePolygon2DList *Polygon2DList - if _, ok := theStrToPolygon2DListMap[key]; ok { - pThePolygon2DList = theStrToPolygon2DListMap[key] - } else { - pThePolygon2DList = &Polygon2DList{ - Eles: make([]*Polygon2D, 0), - } - theStrToPolygon2DListMap[key] = pThePolygon2DList + var pThePolygon2DList *[]*Polygon2D = nil + if _, ok := theStrToPolygon2DListMap[key]; !ok { + tmp := make([]*Polygon2D, 0) + theStrToPolygon2DListMap[key] = &tmp } + pThePolygon2DList = theStrToPolygon2DListMap[key] thePolygon2DFromPolyline, err := tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns) if nil != err { panic(err) } - pThePolygon2DList.Eles = append(pThePolygon2DList.Eles, thePolygon2DFromPolyline) + *pThePolygon2DList = append(*pThePolygon2DList, thePolygon2DFromPolyline) } } return nil @@ -348,13 +345,10 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP for _, objGroup := range pTmxMapIns.ObjectGroups { switch objGroup.Name { case "PlayerStartingPos": - var pTheVec2DListToCache *Vec2DList - _, ok := toRetStrToVec2DListMap[objGroup.Name] - if false == ok { - pTheVec2DListToCache = &Vec2DList{ - Eles: make([]*Vec2D, 0), - } - toRetStrToVec2DListMap[objGroup.Name] = pTheVec2DListToCache + var pTheVec2DListToCache *[]*Vec2D = nil + if _, ok := toRetStrToVec2DListMap[objGroup.Name]; !ok { + tmp := make([]*Vec2D, 0) + toRetStrToVec2DListMap[objGroup.Name] = &tmp } pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name] for _, singleObjInTmxFile := range objGroup.Objects { @@ -363,18 +357,16 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP Y: singleObjInTmxFile.Y, } thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos) - pTheVec2DListToCache.Eles = append(pTheVec2DListToCache.Eles, &thePosInWorld) + *pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld) } case "Barrier": // Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]". - var pThePolygon2DListToCache *Polygon2DList - _, ok := toRetStrToPolygon2DListMap[objGroup.Name] - if false == ok { - pThePolygon2DListToCache = &Polygon2DList{ - Eles: make([]*Polygon2D, 0), - } - toRetStrToPolygon2DListMap[objGroup.Name] = pThePolygon2DListToCache + var pThePolygon2DListToCache *[]*Polygon2D = nil + if _, ok := toRetStrToPolygon2DListMap[objGroup.Name]; !ok { + tmp := make([]*Polygon2D, 0) + toRetStrToPolygon2DListMap[objGroup.Name] = &tmp } + pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name] for _, singleObjInTmxFile := range objGroup.Objects { if nil == singleObjInTmxFile.Polyline { @@ -408,7 +400,7 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToP if nil != err { panic(err) } - pThePolygon2DListToCache.Eles = append(pThePolygon2DListToCache.Eles, thePolygon2DInWorld) + *pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld) } default: } diff --git a/frontend/assets/plugin_scripts/jsexport.js b/frontend/assets/plugin_scripts/jsexport.js new file mode 100644 index 0000000..5b4e520 --- /dev/null +++ b/frontend/assets/plugin_scripts/jsexport.js @@ -0,0 +1,22 @@ +"use strict"; +(function() { + +var $goVersion = "go1.18.6"; +Error.stackTraceLimit=1/0;var $global,$module,$NaN=NaN;if("undefined"!=typeof window?$global=window:"undefined"!=typeof self?$global=self:"undefined"!=typeof global?($global=global).require=require:$global=this,void 0===$global||void 0===$global.Array)throw new Error("no global object found");if("undefined"!=typeof module&&($module=module),!$global.fs&&$global.require)try{var fs=$global.require("fs");"object"==typeof fs&&null!==fs&&0!==Object.keys(fs).length&&($global.fs=fs)}catch(e){}if(!$global.fs){var outputBuf="",decoder=new TextDecoder("utf-8");$global.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync:function(e,n){var r=(outputBuf+=decoder.decode(n)).lastIndexOf("\n");return-1!=r&&(console.log(outputBuf.substr(0,r)),outputBuf=outputBuf.substr(r+1)),n.length},write:function(e,n,r,t,i,a){0===r&&t===n.length&&null===i?a(null,this.writeSync(e,n)):a(enosys())}}}var $throwRuntimeError,$linknames={},$packages={},$idCounter=0,$keys=function(e){return e?Object.keys(e):[]},$flushConsole=function(){},$throwNilPointerError=function(){$throwRuntimeError("invalid memory address or nil pointer dereference")},$call=function(e,n,r){return e.apply(n,r)},$makeFunc=function(e){return function(){return $externalize(e(this,new($sliceType($jsObjectPtr))($global.Array.prototype.slice.call(arguments,[]))),$emptyInterface)}},$unused=function(e){},$print=console.log;if(void 0!==$global.process&&$global.require)try{var util=$global.require("util");$print=function(){$global.process.stderr.write(util.format.apply(this,arguments))}}catch(e){}var $println=console.log,$initAllLinknames=function(){for(var e=$keys($packages),n=0;ne.$capacity||t>e.$capacity)&&$throwRuntimeError("slice bounds out of range"),e===e.constructor.nil)return e;var i=new e.constructor(e.$array);return i.$offset=e.$offset+n,i.$length=r-n,i.$capacity=t-n,i},$substring=function(e,n,r){return(n<0||re.length)&&$throwRuntimeError("slice bounds out of range"),e.substring(n,r)},$sliceToNativeArray=function(e){return e.$array.constructor!==Array?e.$array.subarray(e.$offset,e.$offset+e.$length):e.$array.slice(e.$offset,e.$offset+e.$length)},$sliceToGoArray=function(e,n){var r=n.elem;return void 0!==r&&e.$length1114111||55296<=e&&e<=57343)&&(e=65533),e<=127?String.fromCharCode(e):e<=2047?String.fromCharCode(192|e>>6,128|63&e):e<=65535?String.fromCharCode(224|e>>12,128|e>>6&63,128|63&e):String.fromCharCode(240|e>>18,128|e>>12&63,128|e>>6&63,128|63&e)},$stringToBytes=function(e){for(var n=new Uint8Array(e.length),r=0;rt){for(var o=i-1;o>=0;o--)a.copy(e[r+o],n[t+o]);return}for(o=0;ot)for(o=i-1;o>=0;o--)e[r+o]=n[t+o];else for(o=0;oc)if(a=0,c=Math.max(o,e.$capacity<1024?2*e.$capacity:Math.floor(5*e.$capacity/4)),e.$array.constructor===Array){(i=e.$array.slice(e.$offset,e.$offset+e.$length)).length=c;for(var $=e.constructor.elem.zero,u=e.$length;u>>16&65535)*t+r*(n>>>16&65535)<<16>>>0)>>0},$floatKey=function(e){return e!=e?"NaN$"+ ++$idCounter:String(e)},$flatten64=function(e){return 4294967296*e.$high+e.$low},$shiftLeft64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high<>>32-n,e.$low<>>0):n<64?new e.constructor(e.$low<>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(e.$high>>31,e.$high>>n-32>>>0):e.$high<0?new e.constructor(-1,4294967295):new e.constructor(0,0)},$shiftRightUint64=function(e,n){return 0===n?e:n<32?new e.constructor(e.$high>>>n,(e.$low>>>n|e.$high<<32-n)>>>0):n<64?new e.constructor(0,e.$high>>>n-32):new e.constructor(0,0)},$mul64=function(e,n){var r=e.$high>>>16,t=65535&e.$high,i=e.$low>>>16,a=65535&e.$low,o=n.$high>>>16,c=65535&n.$high,$=n.$low>>>16,u=65535&n.$low,l=0,s=0,f=0,d=0;f+=(d+=a*u)>>>16,s+=(f+=i*u)>>>16,f&=65535,s+=(f+=a*$)>>>16,l+=(s+=t*u)>>>16,s&=65535,l+=(s+=i*$)>>>16,s&=65535,l+=(s+=a*c)>>>16,l+=r*u+t*$+i*c+a*o;var p=((l&=65535)<<16|(s&=65535))>>>0,h=((f&=65535)<<16|(d&=65535))>>>0;return new e.constructor(p,h)},$div64=function(e,n,r){0===n.$high&&0===n.$low&&$throwRuntimeError("integer divide by zero");var t=1,i=1,a=e.$high,o=e.$low;a<0&&(t=-1,i=-1,a=-a,0!==o&&(a--,o=4294967296-o));var c=n.$high,$=n.$low;n.$high<0&&(t*=-1,c=-c,0!==$&&(c--,$=4294967296-$));for(var u=0,l=0,s=0;c<2147483648&&(a>c||a===c&&o>$);)c=(c<<1|$>>>31)>>>0,$=$<<1>>>0,s++;for(var f=0;f<=s;f++)u=u<<1|l>>>31,l=l<<1>>>0,(a>c||a===c&&o>=$)&&(a-=c,(o-=$)<0&&(a--,o+=4294967296),4294967296===++l&&(u++,l=0)),$=($>>>1|c<<31)>>>0,c>>>=1;return r?new e.constructor(a*i,o*i):new e.constructor(u*t,l*t)},$divComplex=function(e,n){var r=e.$real===1/0||e.$real===-1/0||e.$imag===1/0||e.$imag===-1/0,t=n.$real===1/0||n.$real===-1/0||n.$imag===1/0||n.$imag===-1/0,i=!r&&(e.$real!=e.$real||e.$imag!=e.$imag),a=!t&&(n.$real!=n.$real||n.$imag!=n.$imag);if(i||a)return new e.constructor(NaN,NaN);if(r&&!t)return new e.constructor(1/0,1/0);if(!r&&t)return new e.constructor(0,0);if(0===n.$real&&0===n.$imag)return 0===e.$real&&0===e.$imag?new e.constructor(NaN,NaN):new e.constructor(1/0,1/0);if(Math.abs(n.$real)<=Math.abs(n.$imag)){var o=n.$real/n.$imag,c=n.$real*o+n.$imag;return new e.constructor((e.$real*o+e.$imag)/c,(e.$imag*o-e.$real)/c)}o=n.$imag/n.$real,c=n.$imag*o+n.$real;return new e.constructor((e.$imag*o+e.$real)/c,(e.$imag-e.$real*o)/c)},$kindBool=1,$kindInt=2,$kindInt8=3,$kindInt16=4,$kindInt32=5,$kindInt64=6,$kindUint=7,$kindUint8=8,$kindUint16=9,$kindUint32=10,$kindUint64=11,$kindUintptr=12,$kindFloat32=13,$kindFloat64=14,$kindComplex64=15,$kindComplex128=16,$kindArray=17,$kindChan=18,$kindFunc=19,$kindInterface=20,$kindMap=21,$kindPtr=22,$kindSlice=23,$kindString=24,$kindStruct=25,$kindUnsafePointer=26,$methodSynthesizers=[],$addMethodSynthesizer=function(e){null!==$methodSynthesizers?$methodSynthesizers.push(e):e()},$synthesizeMethods=function(){$methodSynthesizers.forEach(function(e){e()}),$methodSynthesizers=null},$ifaceKeyFor=function(e){if(e===$ifaceNil)return"nil";var n=e.constructor;return n.string+"$"+n.keyFor(e.$val)},$identity=function(e){return e},$typeIDCounter=0,$idKey=function(e){return void 0===e.$id&&($idCounter++,e.$id=$idCounter),String(e.$id)},$arrayPtrCtor=function(){return function(e){this.$get=function(){return e},this.$set=function(e){typ.copy(this,e)},this.$val=e}},$newType=function(e,n,r,t,i,a,o){var c;switch(n){case $kindBool:case $kindInt:case $kindInt8:case $kindInt16:case $kindInt32:case $kindUint:case $kindUint8:case $kindUint16:case $kindUint32:case $kindUintptr:case $kindUnsafePointer:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$identity;break;case $kindString:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return"$"+e};break;case $kindFloat32:case $kindFloat64:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=function(e){return $floatKey(e)};break;case $kindInt64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+"$"+e.$low};break;case $kindUint64:(c=function(e,n){this.$high=e+Math.floor(Math.ceil(n)/4294967296)>>>0,this.$low=n>>>0,this.$val=this}).keyFor=function(e){return e.$high+"$"+e.$low};break;case $kindComplex64:(c=function(e,n){this.$real=$fround(e),this.$imag=$fround(n),this.$val=this}).keyFor=function(e){return e.$real+"$"+e.$imag};break;case $kindComplex128:(c=function(e,n){this.$real=e,this.$imag=n,this.$val=this}).keyFor=function(e){return e.$real+"$"+e.$imag};break;case $kindArray:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,"*"+r,!1,"",!1,$arrayPtrCtor()),c.init=function(e,n){c.elem=e,c.len=n,c.comparable=e.comparable,c.keyFor=function(n){return Array.prototype.join.call($mapArray(n,function(n){return String(e.keyFor(n)).replace(/\\/g,"\\\\").replace(/\$/g,"\\$")}),"$")},c.copy=function(n,r){$copyArray(n,r,0,0,r.length,e)},c.ptr.init(c),Object.defineProperty(c.ptr.nil,"nilCheck",{get:$throwNilPointerError})};break;case $kindChan:(c=function(e){this.$val=e}).wrapped=!0,c.keyFor=$idKey,c.init=function(e,n,r){c.elem=e,c.sendOnly=n,c.recvOnly=r};break;case $kindFunc:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n,r){c.params=e,c.results=n,c.variadic=r,c.comparable=!1};break;case $kindInterface:(c={implementedBy:{},missingMethodFor:{}}).keyFor=$ifaceKeyFor,c.init=function(e){c.methods=e,e.forEach(function(e){$ifaceNil[e.prop]=$throwNilPointerError})};break;case $kindMap:(c=function(e){this.$val=e}).wrapped=!0,c.init=function(e,n){c.key=e,c.elem=n,c.comparable=!1};break;case $kindPtr:(c=o||function(e,n,r){this.$get=e,this.$set=n,this.$target=r,this.$val=this}).keyFor=$idKey,c.init=function(e){c.elem=e,c.wrapped=e.kind===$kindArray,c.nil=new c($throwNilPointerError,$throwNilPointerError)};break;case $kindSlice:(c=function(e){e.constructor!==c.nativeArray&&(e=new c.nativeArray(e)),this.$array=e,this.$offset=0,this.$length=e.length,this.$capacity=e.length,this.$val=this}).init=function(e){c.elem=e,c.comparable=!1,c.nativeArray=$nativeArray(e.kind),c.nil=new c([])};break;case $kindStruct:(c=function(e){this.$val=e}).wrapped=!0,c.ptr=$newType(4,$kindPtr,"*"+r,!1,i,a,o),c.ptr.elem=c,c.ptr.prototype.$get=function(){return this},c.ptr.prototype.$set=function(e){c.copy(this,e)},c.init=function(e,n){c.pkgPath=e,c.fields=n,n.forEach(function(e){e.typ.comparable||(c.comparable=!1)}),c.keyFor=function(e){var r=e.$val;return $mapArray(n,function(e){return String(e.typ.keyFor(r[e.prop])).replace(/\\/g,"\\\\").replace(/\$/g,"\\$")}).join("$")},c.copy=function(e,r){for(var t=0;t0;){var a=[],o=[];t.forEach(function(e){if(!i[e.typ.string])switch(i[e.typ.string]=!0,e.typ.named&&(o=o.concat(e.typ.methods),e.indirect&&(o=o.concat($ptrType(e.typ).methods))),e.typ.kind){case $kindStruct:e.typ.fields.forEach(function(n){if(n.embedded){var r=n.typ,t=r.kind===$kindPtr;a.push({typ:t?r.elem:r,indirect:e.indirect||t})}});break;case $kindInterface:o=o.concat(e.typ.methods)}}),o.forEach(function(e){void 0===n[e.name]&&(n[e.name]=e)}),t=a}return e.methodSetCache=[],Object.keys(n).sort().forEach(function(r){e.methodSetCache.push(n[r])}),e.methodSetCache},$Bool=$newType(1,$kindBool,"bool",!0,"",!1,null),$Int=$newType(4,$kindInt,"int",!0,"",!1,null),$Int8=$newType(1,$kindInt8,"int8",!0,"",!1,null),$Int16=$newType(2,$kindInt16,"int16",!0,"",!1,null),$Int32=$newType(4,$kindInt32,"int32",!0,"",!1,null),$Int64=$newType(8,$kindInt64,"int64",!0,"",!1,null),$Uint=$newType(4,$kindUint,"uint",!0,"",!1,null),$Uint8=$newType(1,$kindUint8,"uint8",!0,"",!1,null),$Uint16=$newType(2,$kindUint16,"uint16",!0,"",!1,null),$Uint32=$newType(4,$kindUint32,"uint32",!0,"",!1,null),$Uint64=$newType(8,$kindUint64,"uint64",!0,"",!1,null),$Uintptr=$newType(4,$kindUintptr,"uintptr",!0,"",!1,null),$Float32=$newType(4,$kindFloat32,"float32",!0,"",!1,null),$Float64=$newType(8,$kindFloat64,"float64",!0,"",!1,null),$Complex64=$newType(8,$kindComplex64,"complex64",!0,"",!1,null),$Complex128=$newType(16,$kindComplex128,"complex128",!0,"",!1,null),$String=$newType(8,$kindString,"string",!0,"",!1,null),$UnsafePointer=$newType(4,$kindUnsafePointer,"unsafe.Pointer",!0,"unsafe",!1,null),$nativeArray=function(e){switch(e){case $kindInt:return Int32Array;case $kindInt8:return Int8Array;case $kindInt16:return Int16Array;case $kindInt32:return Int32Array;case $kindUint:return Uint32Array;case $kindUint8:return Uint8Array;case $kindUint16:return Uint16Array;case $kindUint32:case $kindUintptr:return Uint32Array;case $kindFloat32:return Float32Array;case $kindFloat64:return Float64Array;default:return Array}},$toNativeArray=function(e,n){var r=$nativeArray(e);return r===Array?n:new r(n)},$arrayTypes={},$arrayType=function(e,n){var r=e.id+"$"+n,t=$arrayTypes[r];return void 0===t&&(t=$newType(12,$kindArray,"["+n+"]"+e.string,!1,"",!1,null),$arrayTypes[r]=t,t.init(e,n)),t},$chanType=function(e,n,r){var t=(r?"<-":"")+"chan"+(n?"<- ":" ");n||r||"<"!=e.string[0]?t+=e.string:t+="("+e.string+")";var i=n?"SendChan":r?"RecvChan":"Chan",a=e[i];return void 0===a&&(a=$newType(4,$kindChan,t,!1,"",!1,null),e[i]=a,a.init(e,n,r)),a},$Chan=function(e,n){(n<0||n>2147483647)&&$throwRuntimeError("makechan: size out of range"),this.$elem=e,this.$capacity=n,this.$buffer=[],this.$sendQueue=[],this.$recvQueue=[],this.$closed=!1},$chanNil=new $Chan(null,0);$chanNil.$sendQueue=$chanNil.$recvQueue={length:0,push:function(){},shift:function(){},indexOf:function(){return-1}};var $funcTypes={},$funcType=function(e,n,r){var t=$mapArray(e,function(e){return e.id}).join(",")+"$"+$mapArray(n,function(e){return e.id}).join(",")+"$"+r,i=$funcTypes[t];if(void 0===i){var a=$mapArray(e,function(e){return e.string});r&&(a[a.length-1]="..."+a[a.length-1].substr(2));var o="func("+a.join(", ")+")";1===n.length?o+=" "+n[0].string:n.length>1&&(o+=" ("+$mapArray(n,function(e){return e.string}).join(", ")+")"),i=$newType(4,$kindFunc,o,!1,"",!1,null),$funcTypes[t]=i,i.init(e,n,r)}return i},$interfaceTypes={},$interfaceType=function(e){var n=$mapArray(e,function(e){return e.pkg+","+e.name+","+e.typ.id}).join("$"),r=$interfaceTypes[n];if(void 0===r){var t="interface {}";0!==e.length&&(t="interface { "+$mapArray(e,function(e){return(""!==e.pkg?e.pkg+".":"")+e.name+e.typ.string.substr(4)}).join("; ")+" }"),r=$newType(8,$kindInterface,t,!1,"",!1,null),$interfaceTypes[n]=r,r.init(e)}return r},$emptyInterface=$interfaceType([]),$ifaceNil={},$error=$newType(8,$kindInterface,"error",!0,"",!1,null);$error.init([{prop:"Error",name:"Error",pkg:"",typ:$funcType([],[$String],!1)}]);var $panicValue,$jsObjectPtr,$jsErrorPtr,$mapTypes={},$mapType=function(e,n){var r=e.id+"$"+n.id,t=$mapTypes[r];return void 0===t&&(t=$newType(4,$kindMap,"map["+e.string+"]"+n.string,!1,"",!1,null),$mapTypes[r]=t,t.init(e,n)),t},$makeMap=function(e,n){for(var r={},t=0;t2147483647)&&$throwRuntimeError("makeslice: len out of range"),(r<0||r2147483647)&&$throwRuntimeError("makeslice: cap out of range");var t=new e.nativeArray(r);if(e.nativeArray===Array)for(var i=0;i4||t<0)break}}finally{0==$scheduled.length&&clearTimeout(e)}},$schedule=function(e){e.asleep&&(e.asleep=!1,$awakeGoroutines++),$scheduled.push(e),$curGoroutine===$noGoroutine&&$runScheduled()},$setTimeout=function(e,n){return $awakeGoroutines++,setTimeout(function(){$awakeGoroutines--,e()},n)},$block=function(){$curGoroutine===$noGoroutine&&$throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"),$curGoroutine.asleep=!0},$restore=function(e,n){return void 0!==e&&void 0!==e.$blk?e:n},$send=function(e,n){e.$closed&&$throwRuntimeError("send on closed channel");var r=e.$recvQueue.shift();if(void 0===r){if(!(e.$buffer.length65535){var l=Math.floor((u-65536)/1024)+55296,s=(u-65536)%1024+56320;$+=String.fromCharCode(l,s)}else $+=String.fromCharCode(u)}return $;case $kindStruct:var f=$packages.time;if(void 0!==f&&e.constructor===f.Time.ptr){var d=$div64(e.UnixNano(),new $Int64(0,1e6));return new Date($flatten64(d))}var p={},h=function(e,n){if(n===$jsObjectPtr)return e;switch(n.kind){case $kindPtr:return e===n.nil?p:h(e.$get(),n.elem);case $kindStruct:var r=n.fields[0];return h(e[r.prop],r.typ);case $kindInterface:return h(e.$val,e.constructor);default:return p}},k=h(e,n);if(k!==p)return k;if(void 0!==r)return r(e);k={};for(a=0;a>24;case $kindInt16:return parseInt(e)<<16>>16;case $kindInt32:return parseInt(e)>>0;case $kindUint:return parseInt(e);case $kindUint8:return parseInt(e)<<24>>>24;case $kindUint16:return parseInt(e)<<16>>>16;case $kindUint32:case $kindUintptr:return parseInt(e)>>>0;case $kindInt64:case $kindUint64:return new n(0,e);case $kindFloat32:case $kindFloat64:return parseFloat(e);case $kindArray:return e.length!==n.len&&$throwRuntimeError("got array with wrong size from JavaScript native"),$mapArray(e,function(e){return $internalize(e,n.elem,i)});case $kindFunc:return function(){for(var t=[],a=0;a=128)return!1;return!0}; + +$packages["github.com/gopherjs/gopherjs/js"]=(function(){var $pkg={},$init,A,B,J,K,M,O,P,Q,R,S,T,G,H,L;A=$pkg.Object=$newType(0,$kindStruct,"js.Object",true,"github.com/gopherjs/gopherjs/js",true,function(object_){this.$val=this;if(arguments.length===0){this.object=null;return;}this.object=object_;});B=$pkg.Error=$newType(0,$kindStruct,"js.Error",true,"github.com/gopherjs/gopherjs/js",true,function(Object_){this.$val=this;if(arguments.length===0){this.Object=null;return;}this.Object=Object_;});J=$pkg.M=$newType(4,$kindMap,"js.M",true,"github.com/gopherjs/gopherjs/js",true,null);K=$pkg.S=$newType(12,$kindSlice,"js.S",true,"github.com/gopherjs/gopherjs/js",true,null);M=$sliceType($emptyInterface);O=$ptrType(A);P=$sliceType(O);Q=$funcType([P],[O],true);R=$funcType([],[O],false);S=$funcType([O],[],false);T=$ptrType(B);A.ptr.prototype.Get=function(a){var a,b;b=this;return b.object[$externalize(a,$String)];};A.prototype.Get=function(a){return this.$val.Get(a);};A.ptr.prototype.Set=function(a,b){var a,b,c;c=this;c.object[$externalize(a,$String)]=$externalize(b,$emptyInterface);};A.prototype.Set=function(a,b){return this.$val.Set(a,b);};A.ptr.prototype.Delete=function(a){var a,b;b=this;delete b.object[$externalize(a,$String)];};A.prototype.Delete=function(a){return this.$val.Delete(a);};A.ptr.prototype.Length=function(){var a;a=this;return $parseInt(a.object.length);};A.prototype.Length=function(){return this.$val.Length();};A.ptr.prototype.Index=function(a){var a,b;b=this;return b.object[a];};A.prototype.Index=function(a){return this.$val.Index(a);};A.ptr.prototype.SetIndex=function(a,b){var a,b,c;c=this;c.object[a]=$externalize(b,$emptyInterface);};A.prototype.SetIndex=function(a,b){return this.$val.SetIndex(a,b);};A.ptr.prototype.Call=function(a,b){var a,b,c,d;c=this;return(d=c.object,d[$externalize(a,$String)].apply(d,$externalize(b,M)));};A.prototype.Call=function(a,b){return this.$val.Call(a,b);};A.ptr.prototype.Invoke=function(a){var a,b;b=this;return b.object.apply(undefined,$externalize(a,M));};A.prototype.Invoke=function(a){return this.$val.Invoke(a);};A.ptr.prototype.New=function(a){var a,b;b=this;return new($global.Function.prototype.bind.apply(b.object,[undefined].concat($externalize(a,M))));};A.prototype.New=function(a){return this.$val.New(a);};A.ptr.prototype.Bool=function(){var a;a=this;return!!(a.object);};A.prototype.Bool=function(){return this.$val.Bool();};A.ptr.prototype.String=function(){var a;a=this;return $internalize(a.object,$String);};A.prototype.String=function(){return this.$val.String();};A.ptr.prototype.Int=function(){var a;a=this;return $parseInt(a.object)>>0;};A.prototype.Int=function(){return this.$val.Int();};A.ptr.prototype.Int64=function(){var a;a=this;return $internalize(a.object,$Int64);};A.prototype.Int64=function(){return this.$val.Int64();};A.ptr.prototype.Uint64=function(){var a;a=this;return $internalize(a.object,$Uint64);};A.prototype.Uint64=function(){return this.$val.Uint64();};A.ptr.prototype.Float=function(){var a;a=this;return $parseFloat(a.object);};A.prototype.Float=function(){return this.$val.Float();};A.ptr.prototype.Interface=function(){var a;a=this;return $internalize(a.object,$emptyInterface);};A.prototype.Interface=function(){return this.$val.Interface();};A.ptr.prototype.Unsafe=function(){var a;a=this;return a.object;};A.prototype.Unsafe=function(){return this.$val.Unsafe();};B.ptr.prototype.Error=function(){var a;a=this;return"JavaScript error: "+$internalize(a.Object.message,$String);};B.prototype.Error=function(){return this.$val.Error();};B.ptr.prototype.Stack=function(){var a;a=this;return $internalize(a.Object.stack,$String);};B.prototype.Stack=function(){return this.$val.Stack();};G=function(a){var a,b,c,d,e,f;b=a;c=new($global.Object)();c.__internal_object__=b;d=b.constructor.methods;e=0;while(true){if(!(e<$parseInt(d.length))){break;}f=[f];f[0]=d[e];if(!($internalize(f[0].pkg,$String)==="")){e=e+(1)>>0;continue;}c[$externalize($internalize(f[0].name,$String),$String)]=$externalize((function(f){return function(g){var g;return $externalizeFunction(b[$externalize($internalize(f[0].prop,$String),$String)],f[0].typ,$externalize(true,$Bool)).apply(b,$externalize(g,P));};})(f),Q);e=e+(1)>>0;}return c;};$pkg.MakeWrapper=G;H=function(a){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,$s,$r,$c}=$restore(this,{a});$s=$s||0;s:while(true){switch($s){case 0:b=[b];c=[c];b[0]=a;d=b[0].constructor;c[0]=new($global.Object)();e=(function(b,c){return function(e,f){var e,f;$global.Object.defineProperty(c[0],$externalize(e,$String),$externalize(f,J));};})(b,c);$r=e("__internal_object__",$makeMap($String.keyFor,[{k:"value",v:new $jsObjectPtr(b[0])}]));$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}f=$internalize(d.string,$String);g=$internalize(d.pkg,$String);h="";if(f.charCodeAt(0)===42){h="*";}i=0;while(true){if(!(i>0));break;}i=i+(1)>>0;}j=g+"."+h+f;$r=e("$type",$makeMap($String.keyFor,[{k:"value",v:new $String(j)}]));$s=2;case 2:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}k=null;l=new($global.Array)();m=d.methods;if(!(m===undefined)){l=l.concat(m);}n=d.elem;if(!(n===undefined)){k=n.fields;l=l.concat(n.methods);}else{k=d.fields;}o=0;case 3:if(!(o<$parseInt(l.length))){$s=4;continue;}p=[p];p[0]=l[o];if(!($internalize(p[0].pkg,$String)==="")){o=o+(1)>>0;$s=3;continue;}$r=e($internalize(p[0].prop,$String),$makeMap($String.keyFor,[{k:"value",v:new Q((function(b,c,p){return function(q){var q;return $externalizeFunction(b[0][$externalize($internalize(p[0].prop,$String),$String)],p[0].typ,$externalize(true,$Bool),H).apply(b[0],$externalize(q,P));};})(b,c,p))}]));$s=5;case 5:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}o=o+(1)>>0;$s=3;continue;case 4:if(!(k===undefined)){$s=6;continue;}$s=7;continue;case 6:q=0;case 8:if(!(q<$parseInt(k.length))){$s=9;continue;}r=[r];r[0]=k[q];if(!!!(r[0].exported)){q=q+(1)>>0;$s=8;continue;}$r=e($internalize(r[0].prop,$String),$makeMap($String.keyFor,[{k:"get",v:new R((function(b,c,r){return function(){var s;s=$copyIfRequired(b[0].$val[$externalize($internalize(r[0].prop,$String),$String)],r[0].typ);return $externalize(s,r[0].typ,H);};})(b,c,r))},{k:"set",v:new S((function(b,c,r){return function(s){var s,t;t=$internalize(s,r[0].typ,H);b[0].$val[$externalize($internalize(r[0].prop,$String),$String)]=t;};})(b,c,r))}]));$s=10;case 10:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}q=q+(1)>>0;$s=8;continue;case 9:case 7:$s=-1;return c[0];}return;}var $f={$blk:H,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,$s};return $f;};$pkg.MakeFullWrapper=H;L=function(){var a;a=new B.ptr(null);$unused(a);};O.methods=[{prop:"Get",name:"Get",pkg:"",typ:$funcType([$String],[O],false)},{prop:"Set",name:"Set",pkg:"",typ:$funcType([$String,$emptyInterface],[],false)},{prop:"Delete",name:"Delete",pkg:"",typ:$funcType([$String],[],false)},{prop:"Length",name:"Length",pkg:"",typ:$funcType([],[$Int],false)},{prop:"Index",name:"Index",pkg:"",typ:$funcType([$Int],[O],false)},{prop:"SetIndex",name:"SetIndex",pkg:"",typ:$funcType([$Int,$emptyInterface],[],false)},{prop:"Call",name:"Call",pkg:"",typ:$funcType([$String,M],[O],true)},{prop:"Invoke",name:"Invoke",pkg:"",typ:$funcType([M],[O],true)},{prop:"New",name:"New",pkg:"",typ:$funcType([M],[O],true)},{prop:"Bool",name:"Bool",pkg:"",typ:$funcType([],[$Bool],false)},{prop:"String",name:"String",pkg:"",typ:$funcType([],[$String],false)},{prop:"Int",name:"Int",pkg:"",typ:$funcType([],[$Int],false)},{prop:"Int64",name:"Int64",pkg:"",typ:$funcType([],[$Int64],false)},{prop:"Uint64",name:"Uint64",pkg:"",typ:$funcType([],[$Uint64],false)},{prop:"Float",name:"Float",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Interface",name:"Interface",pkg:"",typ:$funcType([],[$emptyInterface],false)},{prop:"Unsafe",name:"Unsafe",pkg:"",typ:$funcType([],[$Uintptr],false)}];T.methods=[{prop:"Error",name:"Error",pkg:"",typ:$funcType([],[$String],false)},{prop:"Stack",name:"Stack",pkg:"",typ:$funcType([],[$String],false)}];A.init("github.com/gopherjs/gopherjs/js",[{prop:"object",name:"object",embedded:false,exported:false,typ:O,tag:""}]);B.init("",[{prop:"Object",name:"Object",embedded:true,exported:true,typ:O,tag:""}]);J.init($String,$emptyInterface);K.init($emptyInterface);$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:L();}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["runtime"]=(function(){var $pkg={},$init,A,C,D,AT,AZ,BD,AM,E,AU;A=$packages["github.com/gopherjs/gopherjs/js"];C=$pkg._type=$newType(0,$kindStruct,"runtime._type",true,"runtime",false,function(str_){this.$val=this;if(arguments.length===0){this.str="";return;}this.str=str_;});D=$pkg.TypeAssertionError=$newType(0,$kindStruct,"runtime.TypeAssertionError",true,"runtime",true,function(_interface_,concrete_,asserted_,missingMethod_){this.$val=this;if(arguments.length===0){this._interface=AZ.nil;this.concrete=AZ.nil;this.asserted=AZ.nil;this.missingMethod="";return;}this._interface=_interface_;this.concrete=concrete_;this.asserted=asserted_;this.missingMethod=missingMethod_;});AT=$pkg.errorString=$newType(8,$kindString,"runtime.errorString",true,"runtime",false,null);AZ=$ptrType(C);BD=$ptrType(D);C.ptr.prototype.string=function(){var a;a=this;return a.str;};C.prototype.string=function(){return this.$val.string();};C.ptr.prototype.pkgpath=function(){var a;a=this;return"";};C.prototype.pkgpath=function(){return this.$val.pkgpath();};D.ptr.prototype.RuntimeError=function(){};D.prototype.RuntimeError=function(){return this.$val.RuntimeError();};D.ptr.prototype.Error=function(){var a,b,c,d,e;a=this;b="interface";if(!(a._interface===AZ.nil)){b=a._interface.string();}c=a.asserted.string();if(a.concrete===AZ.nil){return"interface conversion: "+b+" is nil, not "+c;}d=a.concrete.string();if(a.missingMethod===""){e="interface conversion: "+b+" is "+d+", not "+c;if(d===c){if(!(a.concrete.pkgpath()===a.asserted.pkgpath())){e=e+(" (types from different packages)");}else{e=e+(" (types from different scopes)");}}return e;}return"interface conversion: "+d+" is not "+c+": missing method "+a.missingMethod;};D.prototype.Error=function(){return this.$val.Error();};E=function(){var a,b;a=$packages[$externalize("github.com/gopherjs/gopherjs/js",$String)];$jsObjectPtr=a.Object.ptr;$jsErrorPtr=a.Error.ptr;$throwRuntimeError=AU;AM=$internalize($goVersion,$String);b=$ifaceNil;b=new D.ptr(AZ.nil,AZ.nil,AZ.nil,"");$unused(b);};AT.prototype.RuntimeError=function(){var a;a=this.$val;};$ptrType(AT).prototype.RuntimeError=function(){return new AT(this.$get()).RuntimeError();};AT.prototype.Error=function(){var a;a=this.$val;return"runtime error: "+(a);};$ptrType(AT).prototype.Error=function(){return new AT(this.$get()).Error();};AU=function(a){var a;$panic(new AT((a)));};AZ.methods=[{prop:"string",name:"string",pkg:"runtime",typ:$funcType([],[$String],false)},{prop:"pkgpath",name:"pkgpath",pkg:"runtime",typ:$funcType([],[$String],false)}];BD.methods=[{prop:"RuntimeError",name:"RuntimeError",pkg:"",typ:$funcType([],[],false)},{prop:"Error",name:"Error",pkg:"",typ:$funcType([],[$String],false)}];AT.methods=[{prop:"RuntimeError",name:"RuntimeError",pkg:"",typ:$funcType([],[],false)},{prop:"Error",name:"Error",pkg:"",typ:$funcType([],[$String],false)}];C.init("runtime",[{prop:"str",name:"str",embedded:false,exported:false,typ:$String,tag:""}]);D.init("runtime",[{prop:"_interface",name:"_interface",embedded:false,exported:false,typ:AZ,tag:""},{prop:"concrete",name:"concrete",embedded:false,exported:false,typ:AZ,tag:""},{prop:"asserted",name:"asserted",embedded:false,exported:false,typ:AZ,tag:""},{prop:"missingMethod",name:"missingMethod",embedded:false,exported:false,typ:$String,tag:""}]);$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:$r=A.$init();$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}AM="";E();}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["math/bits"]=(function(){var $pkg={},$init;$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["math"]=(function(){var $pkg={},$init,B,A,IT,IU,IV,IW,FL,FM,FN,FO,FP,HH,EX,EY,FK,GA,GH,GK,GL,GM,GS,GT,GW,GX,GZ,HA,HD,HI,HL,HM;B=$packages["github.com/gopherjs/gopherjs/js"];A=$packages["math/bits"];IT=$arrayType($Uint32,2);IU=$arrayType($Float32,2);IV=$arrayType($Float64,1);IW=$structType("math",[{prop:"uint32array",name:"uint32array",embedded:false,exported:false,typ:IT,tag:""},{prop:"float32array",name:"float32array",embedded:false,exported:false,typ:IU,tag:""},{prop:"float64array",name:"float64array",embedded:false,exported:false,typ:IV,tag:""}]);EX=function(av,aw){var av,aw;if(GL(av,1)||GL(aw,1)){return GK(1);}else if(GM(av)||GM(aw)){return GW();}else if((av===0)&&(av===aw)){if(GZ(av)){return aw;}return av;}if(av>aw){return av;}return aw;};EY=function(av,aw){var av,aw;if(GL(av,-1)||GL(aw,-1)){return GK(-1);}else if(GM(av)||GM(aw)){return GW();}else if((av===0)&&(av===aw)){if(GZ(av)){return av;}return aw;}if(av>>0)));};$pkg.Abs=FK;GA=function(av){var av;return $parseFloat(FL.cos(av));};$pkg.Cos=GA;GH=function(av){var av;return $parseFloat(FL.floor(av));};$pkg.Floor=GH;GK=function(av){var av;if(av>=0){return FN;}else{return FO;}};$pkg.Inf=GK;GL=function(av,aw){var av,aw;if(av===FN){return aw>=0;}if(av===FO){return aw<=0;}return false;};$pkg.IsInf=GL;GM=function(av){var av,aw;aw=false;aw=!((av===av));return aw;};$pkg.IsNaN=GM;GS=function(av,aw){var av,aw;return EX(av,aw);};$pkg.Max=GS;GT=function(av,aw){var av,aw;return EY(av,aw);};$pkg.Min=GT;GW=function(){return FP;};$pkg.NaN=GW;GX=function(av,aw){var av,aw;if((av===1)||((av===-1)&&((aw===FN)||(aw===FO)))){return 1;}return $parseFloat(FL.pow(av,aw));};$pkg.Pow=GX;GZ=function(av){var av;return av<0||(1/av===FO);};$pkg.Signbit=GZ;HA=function(av){var av;return $parseFloat(FL.sin(av));};$pkg.Sin=HA;HD=function(av){var av;return $parseFloat(FL.sqrt(av));};$pkg.Sqrt=HD;HI=function(){var av;av=new($global.ArrayBuffer)(8);HH.uint32array=new($global.Uint32Array)(av);HH.float32array=new($global.Float32Array)(av);HH.float64array=new($global.Float64Array)(av);};HL=function(av){var av,aw,ax;HH.float64array[0]=av;return(aw=$shiftLeft64((new $Uint64(0,HH.uint32array[1])),32),ax=(new $Uint64(0,HH.uint32array[0])),new $Uint64(aw.$high+ax.$high,aw.$low+ax.$low));};$pkg.Float64bits=HL;HM=function(av){var av;HH.uint32array[0]=((av.$low>>>0));HH.uint32array[1]=(($shiftRightUint64(av,32).$low>>>0));return HH.float64array[0];};$pkg.Float64frombits=HM;$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:$r=B.$init();$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}$r=A.$init();$s=2;case 2:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}HH=new IW.ptr(IT.zero(),IU.zero(),IV.zero());FL=$global.Math;FM=0;FN=1/FM;FO=-1/FM;FP=$parseFloat($NaN);HI();}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["resolv"]=(function(){var $pkg={},$init,A,B,C,N,P,Q,S,U,X,Z,AA,AE,AG,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,K,O,R,T,V,W,Y,AB,AC,AD,AF,AH;A=$packages["math"];B=$pkg.Vector=$newType(12,$kindSlice,"resolv.Vector",true,"resolv",true,null);C=$pkg.Axis=$newType(4,$kindInt,"resolv.Axis",true,"resolv",true,null);N=$pkg.Space=$newType(0,$kindStruct,"resolv.Space",true,"resolv",true,function(Cells_,CellWidth_,CellHeight_){this.$val=this;if(arguments.length===0){this.Cells=AL.nil;this.CellWidth=0;this.CellHeight=0;return;}this.Cells=Cells_;this.CellWidth=CellWidth_;this.CellHeight=CellHeight_;});P=$pkg.Shape=$newType(8,$kindInterface,"resolv.Shape",true,"resolv",true,null);Q=$pkg.Line=$newType(0,$kindStruct,"resolv.Line",true,"resolv",true,function(Start_,End_){this.$val=this;if(arguments.length===0){this.Start=B.nil;this.End=B.nil;return;}this.Start=Start_;this.End=End_;});S=$pkg.ConvexPolygon=$newType(0,$kindStruct,"resolv.ConvexPolygon",true,"resolv",true,function(Points_,X_,Y_,Closed_){this.$val=this;if(arguments.length===0){this.Points=AP.nil;this.X=0;this.Y=0;this.Closed=false;return;}this.Points=Points_;this.X=X_;this.Y=Y_;this.Closed=Closed_;});U=$pkg.ContactSet=$newType(0,$kindStruct,"resolv.ContactSet",true,"resolv",true,function(Points_,MTV_,Center_){this.$val=this;if(arguments.length===0){this.Points=AP.nil;this.MTV=B.nil;this.Center=B.nil;return;}this.Points=Points_;this.MTV=MTV_;this.Center=Center_;});X=$pkg.Circle=$newType(0,$kindStruct,"resolv.Circle",true,"resolv",true,function(X_,Y_,Radius_){this.$val=this;if(arguments.length===0){this.X=0;this.Y=0;this.Radius=0;return;}this.X=X_;this.Y=Y_;this.Radius=Radius_;});Z=$pkg.Projection=$newType(0,$kindStruct,"resolv.Projection",true,"resolv",true,function(Min_,Max_){this.$val=this;if(arguments.length===0){this.Min=0;this.Max=0;return;}this.Min=Min_;this.Max=Max_;});AA=$pkg.Object=$newType(0,$kindStruct,"resolv.Object",true,"resolv",true,function(Shape_,Space_,X_,Y_,W_,H_,TouchingCells_,Data_,ignoreList_,tags_){this.$val=this;if(arguments.length===0){this.Shape=$ifaceNil;this.Space=AM.nil;this.X=0;this.Y=0;this.W=0;this.H=0;this.TouchingCells=AK.nil;this.Data=$ifaceNil;this.ignoreList=false;this.tags=AV.nil;return;}this.Shape=Shape_;this.Space=Space_;this.X=X_;this.Y=Y_;this.W=W_;this.H=H_;this.TouchingCells=TouchingCells_;this.Data=Data_;this.ignoreList=ignoreList_;this.tags=tags_;});AE=$pkg.Collision=$newType(0,$kindStruct,"resolv.Collision",true,"resolv",true,function(checkingObject_,dx_,dy_,Objects_,Cells_){this.$val=this;if(arguments.length===0){this.checkingObject=AN.nil;this.dx=0;this.dy=0;this.Objects=AO.nil;this.Cells=AK.nil;return;}this.checkingObject=checkingObject_;this.dx=dx_;this.dy=dy_;this.Objects=Objects_;this.Cells=Cells_;});AG=$pkg.Cell=$newType(0,$kindStruct,"resolv.Cell",true,"resolv",true,function(X_,Y_,Objects_){this.$val=this;if(arguments.length===0){this.X=0;this.Y=0;this.Objects=AO.nil;return;}this.X=X_;this.Y=Y_;this.Objects=Objects_;});AI=$sliceType($Float64);AJ=$ptrType(AG);AK=$sliceType(AJ);AL=$sliceType(AK);AM=$ptrType(N);AN=$ptrType(AA);AO=$sliceType(AN);AP=$sliceType(B);AQ=$ptrType(Q);AR=$sliceType(AQ);AS=$ptrType(X);AT=$ptrType(S);AU=$ptrType(U);AV=$sliceType($String);AW=$ptrType(AE);AX=$sliceType(C);AY=$mapType(AN,$Bool);B.prototype.Clone=function(){var a,b;a=this;b=$makeSlice(B,a.$length);$copySlice(b,a);return b;};$ptrType(B).prototype.Clone=function(){return this.$get().Clone();};B.prototype.Add=function(a){var a,b,c,d,e,f;b=this;c=b.$length;d=a;e=0;while(true){if(!(e=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]).$length>c){AC($convertSliceType(b,AI),1,$convertSliceType(b,AI),$convertSliceType($subslice(((f<0||f>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]),0,c),AI));}else{AC($convertSliceType(b,AI),1,$convertSliceType(b,AI),$convertSliceType(((f<0||f>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]),AI));}e++;}return b;};$ptrType(B).prototype.Add=function(a){return this.$get().Add(a);};B.prototype.Sub=function(a){var a,b,c,d,e,f;b=this;c=b.$length;d=a;e=0;while(true){if(!(e=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]).$length>c){AC($convertSliceType(b,AI),-1,$convertSliceType($subslice(((f<0||f>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]),0,c),AI),$convertSliceType(b,AI));}else{AC($convertSliceType(b,AI),-1,$convertSliceType(((f<0||f>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]),AI),$convertSliceType(b,AI));}e++;}return b;};$ptrType(B).prototype.Sub=function(a){return this.$get().Sub(a);};B.prototype.Scale=function(a){var a,b;b=this;AD($convertSliceType(b,AI),a,$convertSliceType(b,AI));return b;};$ptrType(B).prototype.Scale=function(a){return this.$get().Scale(a);};B.prototype.Equal=function(a){var a,b,c,d,e;b=this;if(!((b.$length===a.$length))){return false;}c=b;d=0;while(true){if(!(d=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+e])-((e<0||e>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+e]))>1e-08){return false;}d++;}return true;};$ptrType(B).prototype.Equal=function(a){return this.$get().Equal(a);};B.prototype.Magnitude=function(){var a;a=this;return A.Sqrt(a.Magnitude2());};$ptrType(B).prototype.Magnitude=function(){return this.$get().Magnitude();};B.prototype.Magnitude2=function(){var a,b,c,d,e;a=this;b=0;c=a;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);b=b+(e*e);d++;}return b;};$ptrType(B).prototype.Magnitude2=function(){return this.$get().Magnitude2();};B.prototype.Unit=function(){var a,b,c,d,e;a=this;b=a.Magnitude();if(b<1e-08){return a;}c=a;d=0;while(true){if(!(d=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+e]=((e<0||e>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+e])/b);d++;}return a;};$ptrType(B).prototype.Unit=function(){return this.$get().Unit();};K=function(a,b){var a,b,c,d,e,f,g,h,i,j,k;c=0;d=a.$length;e=b.$length;f=c;g=d;h=e;if(g>h){b=$appendSlice(b,$convertSliceType($makeSlice(B,(g-h>>0)),AI));}if(g>0)),AI));}i=a;j=0;while(true){if(!(j=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+k])*((k<0||k>=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+k]));j++;}return f;};$pkg.Dot=K;B.prototype.Dot=function(a){var a,b;b=this;return K(b,a);};$ptrType(B).prototype.Dot=function(a){return this.$get().Dot(a);};B.prototype.Cross=function(a){var a,b;b=this;if(!((b.$length===3))||!((a.$length===3))){return B.nil;}return new B([(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1])*(2>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+2])-(2>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+2])*(1>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+1]),(2>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+2])*(0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0])-(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])*(2>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+2]),(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])*(2>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+2])-(2>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+2])*(0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0])]);};$ptrType(B).prototype.Cross=function(a){return this.$get().Cross(a);};B.prototype.Rotate=function(a,b){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;c=this;d=2;e=c.$length;f=d;g=e;if(g===0){return c;}if(b.$length>0){f=(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0]);}if((g===1)&&!((f===2))){c=$append(c,0,0);}if((g<2&&(f===2))||((g===2)&&!((f===2)))){c=$append(c,0);}h=(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]);i=(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]);j=h;k=i;l=A.Cos(a);m=A.Sin(a);n=l;o=m;p=f;if(p===(0)){q=(2>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+2]);(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=k*n-q*o);(2>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+2]=k*o+q*n);}else if(p===(1)){r=(2>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+2]);(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=j*n+r*o);(2>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+2]=-j*o+r*n);}else if(p===(2)){(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=j*n-k*o);(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=j*o+k*n);}if(g>3){return $subslice(c,0,3);}return c;};$ptrType(B).prototype.Rotate=function(a,b){return this.$get().Rotate(a,b);};B.prototype.X=function(){var a;a=this;if(a.$length<1){return 0;}return(0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0]);};$ptrType(B).prototype.X=function(){return this.$get().X();};B.prototype.Y=function(){var a;a=this;if(a.$length<2){return 0;}return(1>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+1]);};$ptrType(B).prototype.Y=function(){return this.$get().Y();};B.prototype.Z=function(){var a;a=this;if(a.$length<3){return 0;}return(2>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+2]);};$ptrType(B).prototype.Z=function(){return this.$get().Z();};O=function(a,b,c,d){var a,b,c,d,e,f,g;e=new N.ptr(AL.nil,c,d);e.Resize((f=a/c,(f===f&&f!==1/0&&f!==-1/0)?f>>0:$throwRuntimeError("integer divide by zero")),(g=b/d,(g===g&&g!==1/0&&g!==-1/0)?g>>0:$throwRuntimeError("integer divide by zero")));return e;};$pkg.NewSpace=O;N.ptr.prototype.Add=function(a){var{a,b,c,d,e,$s,$r,$c}=$restore(this,{a});$s=$s||0;s:while(true){switch($s){case 0:b=this;if(b===AM.nil){$panic(new $String("ERROR: space is nil"));}c=a;d=0;case 1:if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);e.Space=b;$r=e.Update();$s=3;case 3:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}d++;$s=1;continue;case 2:$s=-1;return;}return;}var $f={$blk:N.ptr.prototype.Add,$c:true,$r,a,b,c,d,e,$s};return $f;};N.prototype.Add=function(a){return this.$val.Add(a);};N.ptr.prototype.Remove=function(a){var a,b,c,d,e,f,g,h;b=this;if(b===AM.nil){$panic(new $String("ERROR: space is nil"));}c=a;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);f=e.TouchingCells;g=0;while(true){if(!(g=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+g]);h.unregister(e);g++;}e.TouchingCells=new AK([]);e.Space=AM.nil;d++;}};N.prototype.Remove=function(a){return this.$val.Remove(a);};N.ptr.prototype.Objects=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s;a=this;b=$makeMap(AN.keyFor,[]);c=new AO([]);d=a.Cells;e=0;while(true){if(!(e=h.$length)?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+f]));i=0;while(true){if(!(i=m.$length)?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+f])),((j<0||j>=l.$length)?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+j])).Objects;n=0;while(true){if(!(n=k.$length)?($throwRuntimeError("index out of range"),undefined):k.$array[k.$offset+n]);p=(q=b[AN.keyFor(o)],q!==undefined?[q.v,true]:[false,false]);r=p[1];if(!r){c=$append(c,o);s=o;(b||$throwRuntimeError("assignment to entry in nil map"))[AN.keyFor(s)]={k:s,v:true};}n++;}i++;}e++;}return c;};N.prototype.Objects=function(){return this.$val.Objects();};N.ptr.prototype.Resize=function(a,b){var a,b,c,d,e,f,g;c=this;c.Cells=new AL([]);d=0;while(true){if(!(d=g.$length)?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+d]=$append((f=c.Cells,((d<0||d>=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+d])),AH(e,d))));e=e+(1)>>0;}d=d+(1)>>0;}};N.prototype.Resize=function(a,b){return this.$val.Resize(a,b);};N.ptr.prototype.Cell=function(a,b){var a,b,c,d,e,f;c=this;if(b>=0&&b=0&&a<(d=c.Cells,((b<0||b>=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+b])).$length){return(e=(f=c.Cells,((b<0||b>=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+b])),((a<0||a>=e.$length)?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+a]));}return AJ.nil;};N.prototype.Cell=function(a,b){return this.$val.Cell(a,b);};N.ptr.prototype.CheckCells=function(a,b,c,d,e){var a,b,c,d,e,f,g,h,i,j,k,l,m;f=this;g=a;while(true){if(!(g<(a+c>>0))){break;}h=b;while(true){if(!(h<(b+d>>0))){break;}i=f.Cell(g,h);if(!(i===AJ.nil)){if(e.$length>0){if(i.ContainsTags(e)){j=i.Objects;k=0;while(true){if(!(k=j.$length)?($throwRuntimeError("index out of range"),undefined):j.$array[j.$offset+k]);if(l.HasTags(e)){return l;}k++;}}}else if(i.Occupied()){return(m=i.Objects,(0>=m.$length?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+0]));}}h=h+(1)>>0;}g=g+(1)>>0;}return AN.nil;};N.prototype.CheckCells=function(a,b,c,d,e){return this.$val.CheckCells(a,b,c,d,e);};N.ptr.prototype.CheckCellsWorld=function(a,b,c,d,e){var a,b,c,d,e,f,g,h,i,j,k,l;f=this;g=f.WorldToSpace(a,b);h=g[0];i=g[1];j=f.WorldToSpace(c,d);k=j[0];l=j[1];return f.CheckCells(h,i,k,l,e);};N.prototype.CheckCellsWorld=function(a,b,c,d,e){return this.$val.CheckCellsWorld(a,b,c,d,e);};N.ptr.prototype.UnregisterAllObjects=function(){var a,b,c,d,e,f,g;a=this;b=0;while(true){if(!(b=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+b])).$length)){break;}g=(e=(f=a.Cells,((b<0||b>=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+b])),((c<0||c>=e.$length)?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+c]));a.Remove(g.Objects);c=c+(1)>>0;}b=b+(1)>>0;}};N.prototype.UnregisterAllObjects=function(){return this.$val.UnregisterAllObjects();};N.ptr.prototype.WorldToSpace=function(a,b){var a,b,c,d,e;c=this;d=((A.Floor(a/(c.CellWidth))>>0));e=((A.Floor(b/(c.CellHeight))>>0));return[d,e];};N.prototype.WorldToSpace=function(a,b){return this.$val.WorldToSpace(a,b);};N.ptr.prototype.SpaceToWorld=function(a,b){var a,b,c,d,e;c=this;d=(($imul(a,c.CellWidth)));e=(($imul(b,c.CellHeight)));return[d,e];};N.prototype.SpaceToWorld=function(a,b){return this.$val.SpaceToWorld(a,b);};N.ptr.prototype.Height=function(){var a;a=this;return a.Cells.$length;};N.prototype.Height=function(){return this.$val.Height();};N.ptr.prototype.Width=function(){var a,b;a=this;if(a.Cells.$length>0){return(b=a.Cells,(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])).$length;}return 0;};N.prototype.Width=function(){return this.$val.Width();};N.ptr.prototype.CellsInLine=function(a,b,c,d){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v;e=this;f=new AK([]);g=e.Cell(a,b);h=e.Cell(c,d);if(!(g===AJ.nil)&&!(h===AJ.nil)){i=new B([((c-a>>0)),((d-b>>0))]).Unit();(0>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+0]=(0>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+0])*(((j=e.CellWidth/2,(j===j&&j!==1/0&&j!==-1/0)?j>>0:$throwRuntimeError("integer divide by zero")))));(1>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+1]=(1>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+1])*(((k=e.CellHeight/2,(k===k&&k!==1/0&&k!==-1/0)?k>>0:$throwRuntimeError("integer divide by zero")))));l=e.SpaceToWorld(a,b);m=l[0];n=l[1];q=new B([m+((o=e.CellWidth/2,(o===o&&o!==1/0&&o!==-1/0)?o>>0:$throwRuntimeError("integer divide by zero"))),n+((p=e.CellHeight/2,(p===p&&p!==1/0&&p!==-1/0)?p>>0:$throwRuntimeError("integer divide by zero")))]);r=false;while(true){if(!(!(g===AJ.nil))){break;}if(g===h){f=$append(f,g);break;}f=$append(f,g);if(r){(1>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+1]=(1>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+1])+((1>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+1])));}else{(0>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+0]=(0>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+0])+((0>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+0])));}s=e.WorldToSpace((0>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+0]),(1>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+1]));t=s[0];u=s[1];v=e.Cell(t,u);if(!(v===g)){g=v;}r=!r;}}return f;};N.prototype.CellsInLine=function(a,b,c,d){return this.$val.CellsInLine(a,b,c,d);};R=function(a,b,c,d){var a,b,c,d;return new Q.ptr(new B([a,b]),new B([c,d]));};$pkg.NewLine=R;Q.ptr.prototype.Project=function(a){var a,b;b=this;return b.Vector().Scale(a.Dot(b.Start.Sub(new AP([b.End]))));};Q.prototype.Project=function(a){return this.$val.Project(a);};Q.ptr.prototype.Normal=function(){var a,b;a=this;b=a.Vector();return new B([(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1]),-(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])]).Unit();};Q.prototype.Normal=function(){return this.$val.Normal();};Q.ptr.prototype.Vector=function(){var a;a=this;return a.End.Clone().Sub(new AP([a.Start])).Unit();};Q.prototype.Vector=function(){return this.$val.Vector();};Q.ptr.prototype.IntersectionPointsLine=function(a){var a,aa,ab,ac,ad,ae,af,ag,ah,ai,aj,ak,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;b=this;k=((c=b.End,(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]))-(d=b.Start,(0>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+0])))*((e=a.End,(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1]))-(f=a.Start,(1>=f.$length?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+1])))-((g=a.End,(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0]))-(h=a.Start,(0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0])))*((i=b.End,(1>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+1]))-(j=b.Start,(1>=j.$length?($throwRuntimeError("index out of range"),undefined):j.$array[j.$offset+1])));if(!((k===0))){t=((((l=b.Start,(1>=l.$length?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+1]))-(m=a.Start,(1>=m.$length?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+1])))*((n=a.End,(0>=n.$length?($throwRuntimeError("index out of range"),undefined):n.$array[n.$offset+0]))-(o=a.Start,(0>=o.$length?($throwRuntimeError("index out of range"),undefined):o.$array[o.$offset+0]))))-(((p=b.Start,(0>=p.$length?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+0]))-(q=a.Start,(0>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+0])))*((r=a.End,(1>=r.$length?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+1]))-(s=a.Start,(1>=s.$length?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+1]))))+1)/k;ac=((((u=b.Start,(1>=u.$length?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+1]))-(v=a.Start,(1>=v.$length?($throwRuntimeError("index out of range"),undefined):v.$array[v.$offset+1])))*((w=b.End,(0>=w.$length?($throwRuntimeError("index out of range"),undefined):w.$array[w.$offset+0]))-(x=b.Start,(0>=x.$length?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+0]))))-(((y=b.Start,(0>=y.$length?($throwRuntimeError("index out of range"),undefined):y.$array[y.$offset+0]))-(z=a.Start,(0>=z.$length?($throwRuntimeError("index out of range"),undefined):z.$array[z.$offset+0])))*((aa=b.End,(1>=aa.$length?($throwRuntimeError("index out of range"),undefined):aa.$array[aa.$offset+1]))-(ab=b.Start,(1>=ab.$length?($throwRuntimeError("index out of range"),undefined):ab.$array[ab.$offset+1]))))+1)/k;if((0=ad.$length?($throwRuntimeError("index out of range"),undefined):ad.$array[ad.$offset+0]))-(ae=b.Start,(0>=ae.$length?($throwRuntimeError("index out of range"),undefined):ae.$array[ae.$offset+0]));ai=(ag=b.End,(1>=ag.$length?($throwRuntimeError("index out of range"),undefined):ag.$array[ag.$offset+1]))-(ah=b.Start,(1>=ah.$length?($throwRuntimeError("index out of range"),undefined):ah.$array[ah.$offset+1]));return new B([(aj=b.Start,(0>=aj.$length?($throwRuntimeError("index out of range"),undefined):aj.$array[aj.$offset+0]))+(t*af),(ak=b.Start,(1>=ak.$length?($throwRuntimeError("index out of range"),undefined):ak.$array[ak.$offset+1]))+(t*ai)]);}}return B.nil;};Q.prototype.IntersectionPointsLine=function(a){return this.$val.IntersectionPointsLine(a);};Q.ptr.prototype.IntersectionPointsCircle=function(a){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s;b=this;c=new AP([]);d=new B([a.X,a.Y]);e=b.Start.Sub(new AP([d]));f=b.End.Sub(new AP([d]));g=f.Sub(new AP([e]));h=(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0])*(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0])+(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1])*(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1]);i=2*(((0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0])*(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0]))+((1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1])*(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])));j=((0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0])*(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0]))+((1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])*(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1]))-(a.Radius*a.Radius);k=i*i-(4*h*j);if(k<0){}else if(k===0){l=-i/(2*h);if(l>=0&&l<=1){c=$append(c,new B([(m=b.Start,(0>=m.$length?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+0]))+l*(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0]),(n=b.Start,(1>=n.$length?($throwRuntimeError("index out of range"),undefined):n.$array[n.$offset+1]))+l*(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1])]));}}else{o=(-i+A.Sqrt(k))/(2*h);if(o>=0&&o<=1){c=$append(c,new B([(p=b.Start,(0>=p.$length?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+0]))+o*(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0]),(q=b.Start,(1>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+1]))+o*(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1])]));}o=(-i-A.Sqrt(k))/(2*h);if(o>=0&&o<=1){c=$append(c,new B([(r=b.Start,(0>=r.$length?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+0]))+o*(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0]),(s=b.Start,(1>=s.$length?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+1]))+o*(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1])]));}}return c;};Q.prototype.IntersectionPointsCircle=function(a){return this.$val.IntersectionPointsCircle(a);};T=function(a){var a,b;b=new S.ptr(new AP([]),0,0,true);b.AddPoints(a);return b;};$pkg.NewConvexPolygon=T;S.ptr.prototype.Clone=function(){var a,b,c,d,e,f;a=this;b=new AP([]);c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);b=$append(b,e.Clone());d++;}f=T(AI.nil);f.X=a.X;f.Y=a.Y;f.AddPointsVec(b);f.Closed=a.Closed;return f;};S.prototype.Clone=function(){return this.$val.Clone();};S.ptr.prototype.AddPointsVec=function(a){var a,b;b=this;b.Points=$appendSlice(b.Points,a);};S.prototype.AddPointsVec=function(a){return this.$val.AddPointsVec(a);};S.ptr.prototype.AddPoints=function(a){var a,b,c,d;b=this;c=0;while(true){if(!(c=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+c]),(d=c+1>>0,((d<0||d>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+d]))]));c=c+(2)>>0;}};S.prototype.AddPoints=function(a){return this.$val.AddPoints(a);};S.ptr.prototype.Lines=function(){var a,b,c,d,e,f,g,h,i,j;a=this;b=new AR([]);c=a.Transformed();d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);f=(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]);g=e;h=f;if(d<(c.$length-1>>0)){h=(i=d+1>>0,((i<0||i>=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+i]));}else if(!a.Closed){break;}j=R((0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0]),(1>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+1]),(0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0]),(1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1]));b=$append(b,j);d=d+(1)>>0;}return b;};S.prototype.Lines=function(){return this.$val.Lines();};S.ptr.prototype.Transformed=function(){var a,b,c,d,e;a=this;b=new AP([]);c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);b=$append(b,new B([(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0])+a.X,(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])+a.Y]));d++;}return b;};S.prototype.Transformed=function(){return this.$val.Transformed();};S.ptr.prototype.Bounds=function(){var a,b,c,d,e,f,g,h;a=this;b=a.Transformed();e=new B([(c=(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0]),(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0])),(d=(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0]),(1>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+1]))]);f=e.Clone();g=0;while(true){if(!(g=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+g]);if((0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0])<(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0])){(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0]=(0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0]));}else if((0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0])>(0>=f.$length?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+0])){(0>=f.$length?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+0]=(0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0]));}if((1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1])<(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])){(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1]=(1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1]));}else if((1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1])>(1>=f.$length?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+1])){(1>=f.$length?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+1]=(1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1]));}g=g+(1)>>0;}return[e,f];};S.prototype.Bounds=function(){return this.$val.Bounds();};S.ptr.prototype.Position=function(){var a;a=this;return[a.X,a.Y];};S.prototype.Position=function(){return this.$val.Position();};S.ptr.prototype.SetPosition=function(a,b){var a,b,c;c=this;c.X=a;c.Y=b;};S.prototype.SetPosition=function(a,b){return this.$val.SetPosition(a,b);};S.ptr.prototype.SetPositionVec=function(a){var a,b;b=this;b.X=a.X();b.Y=a.Y();};S.prototype.SetPositionVec=function(a){return this.$val.SetPositionVec(a);};S.ptr.prototype.Move=function(a,b){var a,b,c;c=this;c.X=c.X+(a);c.Y=c.Y+(b);};S.prototype.Move=function(a,b){return this.$val.Move(a,b);};S.ptr.prototype.MoveVec=function(a){var a,b;b=this;b.X=b.X+(a.X());b.Y=b.Y+(a.Y());};S.prototype.MoveVec=function(a){return this.$val.MoveVec(a);};S.ptr.prototype.Center=function(){var a,b,c,d,e;a=this;b=new B([0,0]);c=a.Transformed();d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);b.Add(new AP([e]));d++;}(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0]=(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])/((a.Transformed().$length)));(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1]=(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1])/((a.Transformed().$length)));return b;};S.prototype.Center=function(){return this.$val.Center();};S.ptr.prototype.Project=function(a){var a,b,c,d,e,f,g,h,i,j,k;b=this;a=a.Unit();c=b.Transformed();f=a.Dot(new B([(d=(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]),(0>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+0])),(e=(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]),(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1]))]));g=f;h=1;while(true){if(!(h=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+h]),(0>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+0])),(j=((h<0||h>=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+h]),(1>=j.$length?($throwRuntimeError("index out of range"),undefined):j.$array[j.$offset+1]))]));if(kg){g=k;}h=h+(1)>>0;}return new Z.ptr(f,g);};S.prototype.Project=function(a){return this.$val.Project(a);};S.ptr.prototype.SATAxes=function(){var a,b,c,d,e;a=this;b=new AP([]);c=a.Lines();d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);b=$append(b,e.Normal());d++;}return b;};S.prototype.SATAxes=function(){return this.$val.SATAxes();};S.ptr.prototype.PointInside=function(a){var a,b,c,d,e,f,g;b=this;c=R((0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0]),(1>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+1]),(0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0])+9.99999999999e+11,(1>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+1]));d=0;e=b.Lines();f=0;while(true){if(!(f=e.$length)?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+f]);if(!(g.IntersectionPointsLine(c)===B.nil)){d=d+(1)>>0;}f++;}return d===1;};S.prototype.PointInside=function(a){return this.$val.PointInside(a);};V=function(){return new U.ptr(new AP([]),new B([0,0]),new B([0,0]));};$pkg.NewContactSet=V;U.ptr.prototype.LeftmostPoint=function(){var a,b,c,d,e;a=this;b=B.nil;c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(b===B.nil||(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0])<(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])){b=e;}d++;}return b;};U.prototype.LeftmostPoint=function(){return this.$val.LeftmostPoint();};U.ptr.prototype.RightmostPoint=function(){var a,b,c,d,e;a=this;b=B.nil;c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(b===B.nil||(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0])>(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])){b=e;}d++;}return b;};U.prototype.RightmostPoint=function(){return this.$val.RightmostPoint();};U.ptr.prototype.TopmostPoint=function(){var a,b,c,d,e;a=this;b=B.nil;c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(b===B.nil||(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])<(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1])){b=e;}d++;}return b;};U.prototype.TopmostPoint=function(){return this.$val.TopmostPoint();};U.ptr.prototype.BottommostPoint=function(){var a,b,c,d,e;a=this;b=B.nil;c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(b===B.nil||(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1])>(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1])){b=e;}d++;}return b;};U.prototype.BottommostPoint=function(){return this.$val.BottommostPoint();};S.ptr.prototype.Intersection=function(a,b,c){var a,aa,ab,ac,ad,ae,af,ag,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;d=this;e=V();f=d.X;g=d.Y;d.X=d.X+(a);d.Y=d.Y+(b);h=$assertType(c,AS,true);i=h[0];j=h[1];if(j){k=d.Lines();l=0;while(true){if(!(l=k.$length)?($throwRuntimeError("index out of range"),undefined):k.$array[k.$offset+l]);e.Points=$appendSlice(e.Points,m.IntersectionPointsCircle(i));l++;}}else{n=$assertType(c,AT,true);o=n[0];p=n[1];if(p){q=d.Lines();r=0;while(true){if(!(r=q.$length)?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+r]);t=o.Lines();u=0;while(true){if(!(u=t.$length)?($throwRuntimeError("index out of range"),undefined):t.$array[t.$offset+u]);w=s.IntersectionPointsLine(v);if(!(w===B.nil)){e.Points=$append(e.Points,w);}u++;}r++;}}}if(e.Points.$length>0){x=e.Points;y=0;while(true){if(!(y=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+y]);e.Center=e.Center.Add(new AP([z]));y++;}(ab=e.Center,(0>=ab.$length?($throwRuntimeError("index out of range"),undefined):ab.$array[ab.$offset+0]=(aa=e.Center,(0>=aa.$length?($throwRuntimeError("index out of range"),undefined):aa.$array[aa.$offset+0]))/((e.Points.$length))));(ad=e.Center,(1>=ad.$length?($throwRuntimeError("index out of range"),undefined):ad.$array[ad.$offset+1]=(ac=e.Center,(1>=ac.$length?($throwRuntimeError("index out of range"),undefined):ac.$array[ac.$offset+1]))/((e.Points.$length))));ae=d.calculateMTV(e,c);if(!(ae===B.nil)){e.MTV=ae;}}else{e=AU.nil;}if(!(e===AU.nil)&&(!((a===0))||!((b===0)))){af=new B([a,b]).Magnitude();ag=e.MTV.Magnitude();e.MTV=e.MTV.Unit().Scale(ag-af);}d.X=f;d.Y=g;return e;};S.prototype.Intersection=function(a,b,c){return this.$val.Intersection(a,b,c);};S.ptr.prototype.calculateMTV=function(a,b){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;c=this;d=new B([0,0]);e=new B([1.7976931348623157e+308,0]);f=b;if($assertType(f,AT,true)[1]){g=f.$val;h=c.SATAxes();i=0;while(true){if(!(i=h.$length)?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+i]);if(!$clone(c.Project(j),Z).Overlapping($clone(g.Project(j),Z))){return B.nil;}k=$clone(c.Project(j),Z).Overlap($clone(g.Project(j),Z));if(e.Magnitude()>k){e=j.Scale(k);}i++;}l=g.SATAxes();m=0;while(true){if(!(m=l.$length)?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+m]);if(!$clone(c.Project(n),Z).Overlapping($clone(g.Project(n),Z))){return B.nil;}o=$clone(c.Project(n),Z).Overlap($clone(g.Project(n),Z));if(e.Magnitude()>o){e=n.Scale(o);}m++;}}(0>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+0]=(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0]));(1>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+1]=(1>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+1]));return d;};S.prototype.calculateMTV=function(a,b){return this.$val.calculateMTV(a,b);};S.ptr.prototype.ContainedBy=function(a){var a,b,c,d,e,f,g,h,i,j;b=this;c=a;if($assertType(c,AT,true)[1]){d=c.$val;e=b.SATAxes();f=0;while(true){if(!(f=e.$length)?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+f]);if(!$clone(b.Project(g),Z).IsInside($clone(d.Project(g),Z))){return false;}f++;}h=d.SATAxes();i=0;while(true){if(!(i=h.$length)?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+i]);if(!$clone(b.Project(j),Z).IsInside($clone(d.Project(j),Z))){return false;}i++;}}return true;};S.prototype.ContainedBy=function(a){return this.$val.ContainedBy(a);};S.ptr.prototype.FlipH=function(){var a,b,c,d;a=this;b=a.Points;c=0;while(true){if(!(c=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+c]);(0>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+0]=-(0>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+0]));c++;}a.ReverseVertexOrder();};S.prototype.FlipH=function(){return this.$val.FlipH();};S.ptr.prototype.FlipV=function(){var a,b,c,d;a=this;b=a.Points;c=0;while(true){if(!(c=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+c]);(1>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+1]=-(1>=d.$length?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+1]));c++;}a.ReverseVertexOrder();};S.prototype.FlipV=function(){return this.$val.FlipV();};S.ptr.prototype.ReverseVertexOrder=function(){var a,b,c,d,e;a=this;c=new AP([(b=a.Points,(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0]))]);d=a.Points.$length-1>>0;while(true){if(!(d>=1)){break;}c=$append(c,(e=a.Points,((d<0||d>=e.$length)?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+d])));d=d-(1)>>0;}a.Points=c;};S.prototype.ReverseVertexOrder=function(){return this.$val.ReverseVertexOrder();};W=function(a,b,c,d){var a,b,c,d;return T(new AI([a,b,a+c,b,a+c,b+d,a,b+d]));};$pkg.NewRectangle=W;Y=function(a,b,c){var a,b,c,d;d=new X.ptr(a,b,c);return d;};$pkg.NewCircle=Y;X.ptr.prototype.Clone=function(){var a;a=this;return Y(a.X,a.Y,a.Radius);};X.prototype.Clone=function(){return this.$val.Clone();};X.ptr.prototype.Bounds=function(){var a;a=this;return[new B([a.X-a.Radius,a.Y-a.Radius]),new B([a.X+a.Radius,a.Y+a.Radius])];};X.prototype.Bounds=function(){return this.$val.Bounds();};X.ptr.prototype.Intersection=function(a,b,c){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;d=this;e=AU.nil;f=d.X;g=d.Y;d.X=d.X+(a);d.Y=d.Y+(b);h=c;if($assertType(h,AT,true)[1]){i=h.$val;e=i.Intersection(-a,-b,d);if(!(e===AU.nil)){e.MTV=e.MTV.Scale(-1);}}else if($assertType(h,AS,true)[1]){j=h.$val;e=V();e.Points=d.IntersectionPointsCircle(j);if(e.Points.$length===0){return AU.nil;}e.MTV=new B([d.X-j.X,d.Y-j.Y]);k=e.MTV.Magnitude();e.MTV=e.MTV.Unit().Scale(d.Radius+j.Radius-k);l=e.Points;m=0;while(true){if(!(m=l.$length)?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+m]);e.Center=e.Center.Add(new AP([n]));m++;}(p=e.Center,(0>=p.$length?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+0]=(o=e.Center,(0>=o.$length?($throwRuntimeError("index out of range"),undefined):o.$array[o.$offset+0]))/((e.Points.$length))));(r=e.Center,(1>=r.$length?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+1]=(q=e.Center,(1>=q.$length?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+1]))/((e.Points.$length))));}d.X=f;d.Y=g;return e;};X.prototype.Intersection=function(a,b,c){return this.$val.Intersection(a,b,c);};X.ptr.prototype.Move=function(a,b){var a,b,c;c=this;c.X=c.X+(a);c.Y=c.Y+(b);};X.prototype.Move=function(a,b){return this.$val.Move(a,b);};X.ptr.prototype.MoveVec=function(a){var a,b;b=this;b.X=b.X+(a.X());b.Y=b.Y+(a.Y());};X.prototype.MoveVec=function(a){return this.$val.MoveVec(a);};X.ptr.prototype.SetPosition=function(a,b){var a,b,c;c=this;c.X=a;c.Y=b;};X.prototype.SetPosition=function(a,b){return this.$val.SetPosition(a,b);};X.ptr.prototype.SetPositionVec=function(a){var a,b;b=this;b.X=a.X();b.Y=a.Y();};X.prototype.SetPositionVec=function(a){return this.$val.SetPositionVec(a);};X.ptr.prototype.Position=function(){var a;a=this;return[a.X,a.Y];};X.prototype.Position=function(){return this.$val.Position();};X.ptr.prototype.PointInside=function(a){var a,b;b=this;return a.Sub(new AP([new B([b.X,b.Y])])).Magnitude()<=b.Radius;};X.prototype.PointInside=function(a){return this.$val.PointInside(a);};X.ptr.prototype.IntersectionPointsCircle=function(a){var a,b,c,d,e,f,g;b=this;c=A.Sqrt(A.Pow(a.X-b.X,2)+A.Pow(a.Y-b.Y,2));if(c>b.Radius+a.Radius||c0;};Z.prototype.Overlapping=function(a){return this.$val.Overlapping(a);};Z.ptr.prototype.Overlap=function(a){var a,b;b=this;return A.Min(b.Max,a.Max)-A.Max(b.Min,a.Min);};Z.prototype.Overlap=function(a){return this.$val.Overlap(a);};Z.ptr.prototype.IsInside=function(a){var a,b;b=this;return b.Min>=a.Min&&b.Max<=a.Max;};Z.prototype.IsInside=function(a){return this.$val.IsInside(a);};AB=function(a,b,c,d,e){var a,b,c,d,e,f;f=new AA.ptr($ifaceNil,AM.nil,a,b,c,d,AK.nil,$ifaceNil,$makeMap(AN.keyFor,[]),new AV([]));if(e.$length>0){f.AddTags(e);}return f;};$pkg.NewObject=AB;AA.ptr.prototype.Clone=function(){var{a,b,c,d,e,f,g,h,$s,$r,$c}=$restore(this,{});$s=$s||0;s:while(true){switch($s){case 0:a=this;b=AB(a.X,a.Y,a.W,a.H,a.Tags());b.Data=a.Data;if(!($interfaceIsEqual(a.Shape,$ifaceNil))){$s=1;continue;}$s=2;continue;case 1:c=a.Shape.Clone();$s=3;case 3:if($c){$c=false;c=c.$blk();}if(c&&c.$blk!==undefined){break s;}$r=b.SetShape(c);$s=4;case 4:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}case 2:d=a.ignoreList;e=0;f=$keys(d);while(true){if(!(e>0;}h=h+(1)>>0;}}if(!($interfaceIsEqual(a.Shape,$ifaceNil))){$s=1;continue;}$s=2;continue;case 1:$r=a.Shape.SetPosition(a.X,a.Y);$s=3;case 3:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}case 2:$s=-1;return;}return;}var $f={$blk:AA.ptr.prototype.Update,$c:true,$r,a,b,c,d,e,f,g,h,i,j,$s};return $f;};AA.prototype.Update=function(){return this.$val.Update();};AA.ptr.prototype.AddTags=function(a){var a,b;b=this;b.tags=$appendSlice(b.tags,a);};AA.prototype.AddTags=function(a){return this.$val.AddTags(a);};AA.ptr.prototype.RemoveTags=function(a){var a,b,c,d,e,f,g,h,i;b=this;c=a;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);f=b.tags;g=0;while(true){if(!(g=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+g]);if(i===e){b.tags=$appendSlice($subslice(b.tags,0,h),$subslice(b.tags,(h+1>>0)));break;}g++;}d++;}};AA.prototype.RemoveTags=function(a){return this.$val.RemoveTags(a);};AA.ptr.prototype.HasTags=function(a){var a,b,c,d,e,f,g,h;b=this;c=a;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);f=b.tags;g=0;while(true){if(!(g=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+g]);if(h===e){return true;}g++;}d++;}return false;};AA.prototype.HasTags=function(a){return this.$val.HasTags(a);};AA.ptr.prototype.Tags=function(){var a;a=this;return $appendSlice(new AV([]),a.tags);};AA.prototype.Tags=function(){return this.$val.Tags();};AA.ptr.prototype.SetShape=function(a){var{a,b,$s,$r,$c}=$restore(this,{a});$s=$s||0;s:while(true){switch($s){case 0:b=this;if(!($interfaceIsEqual(b.Shape,a))){$s=1;continue;}$s=2;continue;case 1:b.Shape=a;$r=b.Update();$s=3;case 3:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}case 2:$s=-1;return;}return;}var $f={$blk:AA.ptr.prototype.SetShape,$c:true,$r,a,b,$s};return $f;};AA.prototype.SetShape=function(a){return this.$val.SetShape(a);};AA.ptr.prototype.BoundsToSpace=function(a,b){var a,b,c,d,e,f,g,h,i;c=this;d=c.Space.WorldToSpace(c.X+a,c.Y+b);e=d[0];f=d[1];g=c.Space.WorldToSpace(c.X+c.W+a-1,c.Y+c.H+b-1);h=g[0];i=g[1];return[e,f,h,i];};AA.prototype.BoundsToSpace=function(a,b){return this.$val.BoundsToSpace(a,b);};AA.ptr.prototype.SharesCells=function(a){var a,b,c,d,e;b=this;c=b.TouchingCells;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e.Contains(a)){return true;}d++;}return false;};AA.prototype.SharesCells=function(a){return this.$val.SharesCells(a);};AA.ptr.prototype.SharesCellsTags=function(a){var a,b,c,d,e;b=this;c=b.TouchingCells;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e.ContainsTags(a)){return true;}d++;}return false;};AA.prototype.SharesCellsTags=function(a){return this.$val.SharesCellsTags(a);};AA.ptr.prototype.Center=function(){var a;a=this;return[a.X+(a.W/2),a.Y+(a.H/2)];};AA.prototype.Center=function(){return this.$val.Center();};AA.ptr.prototype.SetCenter=function(a,b){var a,b,c;c=this;c.X=a-(c.W/2);c.Y=b-(c.H/2);};AA.prototype.SetCenter=function(a,b){return this.$val.SetCenter(a,b);};AA.ptr.prototype.CellPosition=function(){var a,b;a=this;b=a.Center();return a.Space.WorldToSpace(b[0],b[1]);};AA.prototype.CellPosition=function(){return this.$val.CellPosition();};AA.ptr.prototype.SetRight=function(a){var a,b;b=this;b.X=a-b.W;};AA.prototype.SetRight=function(a){return this.$val.SetRight(a);};AA.ptr.prototype.SetBottom=function(a){var a,b;b=this;b.Y=a-b.H;};AA.prototype.SetBottom=function(a){return this.$val.SetBottom(a);};AA.ptr.prototype.Bottom=function(){var a;a=this;return a.Y+a.H;};AA.prototype.Bottom=function(){return this.$val.Bottom();};AA.ptr.prototype.Right=function(){var a;a=this;return a.X+a.W;};AA.prototype.Right=function(){return this.$val.Right();};AA.ptr.prototype.SetBounds=function(a,b){var a,b,c;c=this;c.X=(0>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+0]);c.Y=(1>=a.$length?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+1]);c.W=(0>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+0])-c.X;c.H=(1>=b.$length?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+1])-c.Y;};AA.prototype.SetBounds=function(a,b){return this.$val.SetBounds(a,b);};AA.ptr.prototype.Check=function(a,b,c){var a,aa,ab,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;d=this;if(d.Space===AM.nil){return AW.nil;}e=AF();e.checkingObject=d;if(a<0){a=A.Min(a,-1);}else if(a>0){a=A.Max(a,1);}if(b<0){b=A.Min(b,-1);}else if(b>0){b=A.Max(b,1);}e.dx=a;e.dy=b;f=d.BoundsToSpace(a,b);g=f[0];h=f[1];i=f[2];j=f[3];k=$makeMap(AN.keyFor,[]);l=$makeMap(AJ.keyFor,[]);m=h;while(true){if(!(m<=j)){break;}n=g;while(true){if(!(n<=i)){break;}o=d.Space.Cell(n,m);if(!(o===AJ.nil)){p=o.Objects;q=0;while(true){if(!(q=p.$length)?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+q]);t=(s=d.ignoreList[AN.keyFor(r)],s!==undefined?s.v:false);if(r===d||t){q++;continue;}u=(v=k[AN.keyFor(r)],v!==undefined?[v.v,true]:[false,false]);w=u[1];if(((c.$length===0)||r.HasTags(c))&&!w){e.Objects=$append(e.Objects,r);x=r;(k||$throwRuntimeError("assignment to entry in nil map"))[AN.keyFor(x)]={k:x,v:true};y=(z=l[AJ.keyFor(o)],z!==undefined?[z.v,true]:[false,false]);aa=y[1];if(!aa){e.Cells=$append(e.Cells,o);ab=o;(l||$throwRuntimeError("assignment to entry in nil map"))[AJ.keyFor(ab)]={k:ab,v:true};}q++;continue;}q++;}}n=n+(1)>>0;}m=m+(1)>>0;}if(e.Objects.$length===0){return AW.nil;}return e;};AA.prototype.Check=function(a,b,c){return this.$val.Check(a,b,c);};AA.ptr.prototype.Overlaps=function(a){var a,b;b=this;return a.X<=b.X+b.W&&a.X+a.W>=b.X&&a.Y<=b.Y+b.H&&a.Y+a.H>=b.Y;};AA.prototype.Overlaps=function(a){return this.$val.Overlaps(a);};AA.ptr.prototype.AddToIgnoreList=function(a){var a,b,c;b=this;c=a;(b.ignoreList||$throwRuntimeError("assignment to entry in nil map"))[AN.keyFor(c)]={k:c,v:true};};AA.prototype.AddToIgnoreList=function(a){return this.$val.AddToIgnoreList(a);};AA.ptr.prototype.RemoveFromIgnoreList=function(a){var a,b;b=this;delete b.ignoreList[AN.keyFor(a)];};AA.prototype.RemoveFromIgnoreList=function(a){return this.$val.RemoveFromIgnoreList(a);};AC=function(a,b,c,d){var a,b,c,d,e,f,g,h,i;e=d.$length;f=c;g=0;while(true){if(!(g=f.$length)?($throwRuntimeError("index out of range"),undefined):f.$array[f.$offset+g]);if(h===e){return;}((h<0||h>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+h]=b*i+((h<0||h>=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+h]));g++;}};AD=function(a,b,c){var a,b,c,d,e,f;d=c;e=0;while(true){if(!(e=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f]=((f<0||f>=a.$length)?($throwRuntimeError("index out of range"),undefined):a.$array[a.$offset+f])*(b));e++;}};AF=function(){return new AE.ptr(AN.nil,0,0,new AO([]),AK.nil);};$pkg.NewCollision=AF;AE.ptr.prototype.HasTags=function(a){var a,b,c,d,e;b=this;c=b.Objects;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e===b.checkingObject){d++;continue;}if(e.HasTags(a)){return true;}d++;}return false;};AE.prototype.HasTags=function(a){return this.$val.HasTags(a);};AE.ptr.prototype.ObjectsByTags=function(a){var a,b,c,d,e,f;b=this;c=new AO([]);d=b.Objects;e=0;while(true){if(!(e=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+e]);if(f===b.checkingObject){e++;continue;}if(f.HasTags(a)){c=$append(c,f);}e++;}return c;};AE.prototype.ObjectsByTags=function(a){return this.$val.ObjectsByTags(a);};AE.ptr.prototype.ContactWithObject=function(a){var a,b,c;b=this;c=new B([0,0]);if(b.dx<0){(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=a.X+a.W-b.checkingObject.X);}else if(b.dx>0){(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=a.X-b.checkingObject.W-b.checkingObject.X);}if(b.dy<0){(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=a.Y+a.H-b.checkingObject.Y);}else if(b.dy>0){(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=a.Y-b.checkingObject.H-b.checkingObject.Y);}return c;};AE.prototype.ContactWithObject=function(a){return this.$val.ContactWithObject(a);};AE.ptr.prototype.ContactWithCell=function(a){var a,b,c,d,e;b=this;c=new B([0,0]);d=(($imul(a.X,b.checkingObject.Space.CellWidth)));e=(($imul(a.Y,b.checkingObject.Space.CellHeight)));if(b.dx<0){(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=d+(b.checkingObject.Space.CellWidth)-b.checkingObject.X);}else if(b.dx>0){(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0]=d-b.checkingObject.W-b.checkingObject.X);}if(b.dy<0){(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=e+(b.checkingObject.Space.CellHeight)-b.checkingObject.Y);}else if(b.dy>0){(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]=e-b.checkingObject.H-b.checkingObject.Y);}return c;};AE.prototype.ContactWithCell=function(a){return this.$val.ContactWithCell(a);};AE.ptr.prototype.SlideAgainstCell=function(a,b){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;c=this;d=c.checkingObject.Space;f=(e=c.Cells,(0>=e.$length?($throwRuntimeError("index out of range"),undefined):e.$array[e.$offset+0]));g=d.SpaceToWorld(f.X,f.Y);h=g[0];i=g[1];j=(d.CellWidth)/2;k=(d.CellHeight)/2;h=h+(j);i=i+(k);l=c.checkingObject.Center();m=l[0];n=l[1];o=m-h;p=n-i;q=d.Cell(f.X-1>>0,f.Y);r=d.Cell(f.X+1>>0,f.Y);s=d.Cell(f.X,f.Y-1>>0);t=d.Cell(f.X,f.Y+1>>0);u=new B([0,0]);if(!((c.dy===0))){if(o>0&&(r===AJ.nil||!r.ContainsTags(b))){(0>=u.$length?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+0]=h+j-c.checkingObject.X);}else if(o<0&&(q===AJ.nil||!q.ContainsTags(b))){(0>=u.$length?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+0]=h-j-(c.checkingObject.X+c.checkingObject.W));}else{return B.nil;}}if(!((c.dx===0))){if(p>0&&(t===AJ.nil||!t.ContainsTags(b))){(1>=u.$length?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+1]=i+k-c.checkingObject.Y);}else if(p<0&&(s===AJ.nil||!s.ContainsTags(b))){(1>=u.$length?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+1]=i-k-(c.checkingObject.Y+c.checkingObject.H));}else{return B.nil;}}return u;};AE.prototype.SlideAgainstCell=function(a,b){return this.$val.SlideAgainstCell(a,b);};AH=function(a,b){var a,b;return new AG.ptr(a,b,new AO([]));};AG.ptr.prototype.register=function(a){var a,b;b=this;if(!b.Contains(a)){b.Objects=$append(b.Objects,a);}};AG.prototype.register=function(a){return this.$val.register(a);};AG.ptr.prototype.unregister=function(a){var a,b,c,d,e,f,g,h,i;b=this;c=b.Objects;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(f===a){(i=b.Objects,((e<0||e>=i.$length)?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+e]=(g=b.Objects,h=b.Objects.$length-1>>0,((h<0||h>=g.$length)?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+h]))));b.Objects=$subslice(b.Objects,0,(b.Objects.$length-1>>0));break;}d++;}};AG.prototype.unregister=function(a){return this.$val.unregister(a);};AG.ptr.prototype.Contains=function(a){var a,b,c,d,e;b=this;c=b.Objects;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e===a){return true;}d++;}return false;};AG.prototype.Contains=function(a){return this.$val.Contains(a);};AG.ptr.prototype.ContainsTags=function(a){var a,b,c,d,e;b=this;c=b.Objects;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e.HasTags(a)){return true;}d++;}return false;};AG.prototype.ContainsTags=function(a){return this.$val.ContainsTags(a);};AG.ptr.prototype.Occupied=function(){var a;a=this;return a.Objects.$length>0;};AG.prototype.Occupied=function(){return this.$val.Occupied();};B.methods=[{prop:"Clone",name:"Clone",pkg:"",typ:$funcType([],[B],false)},{prop:"Add",name:"Add",pkg:"",typ:$funcType([AP],[B],true)},{prop:"Sub",name:"Sub",pkg:"",typ:$funcType([AP],[B],true)},{prop:"Scale",name:"Scale",pkg:"",typ:$funcType([$Float64],[B],false)},{prop:"Equal",name:"Equal",pkg:"",typ:$funcType([B],[$Bool],false)},{prop:"Magnitude",name:"Magnitude",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Magnitude2",name:"Magnitude2",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Unit",name:"Unit",pkg:"",typ:$funcType([],[B],false)},{prop:"Dot",name:"Dot",pkg:"",typ:$funcType([B],[$Float64],false)},{prop:"Cross",name:"Cross",pkg:"",typ:$funcType([B],[B],false)},{prop:"Rotate",name:"Rotate",pkg:"",typ:$funcType([$Float64,AX],[B],true)},{prop:"X",name:"X",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Y",name:"Y",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Z",name:"Z",pkg:"",typ:$funcType([],[$Float64],false)}];AM.methods=[{prop:"Add",name:"Add",pkg:"",typ:$funcType([AO],[],true)},{prop:"Remove",name:"Remove",pkg:"",typ:$funcType([AO],[],true)},{prop:"Objects",name:"Objects",pkg:"",typ:$funcType([],[AO],false)},{prop:"Resize",name:"Resize",pkg:"",typ:$funcType([$Int,$Int],[],false)},{prop:"Cell",name:"Cell",pkg:"",typ:$funcType([$Int,$Int],[AJ],false)},{prop:"CheckCells",name:"CheckCells",pkg:"",typ:$funcType([$Int,$Int,$Int,$Int,AV],[AN],true)},{prop:"CheckCellsWorld",name:"CheckCellsWorld",pkg:"",typ:$funcType([$Float64,$Float64,$Float64,$Float64,AV],[AN],true)},{prop:"UnregisterAllObjects",name:"UnregisterAllObjects",pkg:"",typ:$funcType([],[],false)},{prop:"WorldToSpace",name:"WorldToSpace",pkg:"",typ:$funcType([$Float64,$Float64],[$Int,$Int],false)},{prop:"SpaceToWorld",name:"SpaceToWorld",pkg:"",typ:$funcType([$Int,$Int],[$Float64,$Float64],false)},{prop:"Height",name:"Height",pkg:"",typ:$funcType([],[$Int],false)},{prop:"Width",name:"Width",pkg:"",typ:$funcType([],[$Int],false)},{prop:"CellsInLine",name:"CellsInLine",pkg:"",typ:$funcType([$Int,$Int,$Int,$Int],[AK],false)}];AQ.methods=[{prop:"Project",name:"Project",pkg:"",typ:$funcType([B],[B],false)},{prop:"Normal",name:"Normal",pkg:"",typ:$funcType([],[B],false)},{prop:"Vector",name:"Vector",pkg:"",typ:$funcType([],[B],false)},{prop:"IntersectionPointsLine",name:"IntersectionPointsLine",pkg:"",typ:$funcType([AQ],[B],false)},{prop:"IntersectionPointsCircle",name:"IntersectionPointsCircle",pkg:"",typ:$funcType([AS],[AP],false)}];AT.methods=[{prop:"Clone",name:"Clone",pkg:"",typ:$funcType([],[P],false)},{prop:"AddPointsVec",name:"AddPointsVec",pkg:"",typ:$funcType([AP],[],true)},{prop:"AddPoints",name:"AddPoints",pkg:"",typ:$funcType([AI],[],true)},{prop:"Lines",name:"Lines",pkg:"",typ:$funcType([],[AR],false)},{prop:"Transformed",name:"Transformed",pkg:"",typ:$funcType([],[AP],false)},{prop:"Bounds",name:"Bounds",pkg:"",typ:$funcType([],[B,B],false)},{prop:"Position",name:"Position",pkg:"",typ:$funcType([],[$Float64,$Float64],false)},{prop:"SetPosition",name:"SetPosition",pkg:"",typ:$funcType([$Float64,$Float64],[],false)},{prop:"SetPositionVec",name:"SetPositionVec",pkg:"",typ:$funcType([B],[],false)},{prop:"Move",name:"Move",pkg:"",typ:$funcType([$Float64,$Float64],[],false)},{prop:"MoveVec",name:"MoveVec",pkg:"",typ:$funcType([B],[],false)},{prop:"Center",name:"Center",pkg:"",typ:$funcType([],[B],false)},{prop:"Project",name:"Project",pkg:"",typ:$funcType([B],[Z],false)},{prop:"SATAxes",name:"SATAxes",pkg:"",typ:$funcType([],[AP],false)},{prop:"PointInside",name:"PointInside",pkg:"",typ:$funcType([B],[$Bool],false)},{prop:"Intersection",name:"Intersection",pkg:"",typ:$funcType([$Float64,$Float64,P],[AU],false)},{prop:"calculateMTV",name:"calculateMTV",pkg:"resolv",typ:$funcType([AU,P],[B],false)},{prop:"ContainedBy",name:"ContainedBy",pkg:"",typ:$funcType([P],[$Bool],false)},{prop:"FlipH",name:"FlipH",pkg:"",typ:$funcType([],[],false)},{prop:"FlipV",name:"FlipV",pkg:"",typ:$funcType([],[],false)},{prop:"ReverseVertexOrder",name:"ReverseVertexOrder",pkg:"",typ:$funcType([],[],false)}];AU.methods=[{prop:"LeftmostPoint",name:"LeftmostPoint",pkg:"",typ:$funcType([],[B],false)},{prop:"RightmostPoint",name:"RightmostPoint",pkg:"",typ:$funcType([],[B],false)},{prop:"TopmostPoint",name:"TopmostPoint",pkg:"",typ:$funcType([],[B],false)},{prop:"BottommostPoint",name:"BottommostPoint",pkg:"",typ:$funcType([],[B],false)}];AS.methods=[{prop:"Clone",name:"Clone",pkg:"",typ:$funcType([],[P],false)},{prop:"Bounds",name:"Bounds",pkg:"",typ:$funcType([],[B,B],false)},{prop:"Intersection",name:"Intersection",pkg:"",typ:$funcType([$Float64,$Float64,P],[AU],false)},{prop:"Move",name:"Move",pkg:"",typ:$funcType([$Float64,$Float64],[],false)},{prop:"MoveVec",name:"MoveVec",pkg:"",typ:$funcType([B],[],false)},{prop:"SetPosition",name:"SetPosition",pkg:"",typ:$funcType([$Float64,$Float64],[],false)},{prop:"SetPositionVec",name:"SetPositionVec",pkg:"",typ:$funcType([B],[],false)},{prop:"Position",name:"Position",pkg:"",typ:$funcType([],[$Float64,$Float64],false)},{prop:"PointInside",name:"PointInside",pkg:"",typ:$funcType([B],[$Bool],false)},{prop:"IntersectionPointsCircle",name:"IntersectionPointsCircle",pkg:"",typ:$funcType([AS],[AP],false)}];Z.methods=[{prop:"Overlapping",name:"Overlapping",pkg:"",typ:$funcType([Z],[$Bool],false)},{prop:"Overlap",name:"Overlap",pkg:"",typ:$funcType([Z],[$Float64],false)},{prop:"IsInside",name:"IsInside",pkg:"",typ:$funcType([Z],[$Bool],false)}];AN.methods=[{prop:"Clone",name:"Clone",pkg:"",typ:$funcType([],[AN],false)},{prop:"Update",name:"Update",pkg:"",typ:$funcType([],[],false)},{prop:"AddTags",name:"AddTags",pkg:"",typ:$funcType([AV],[],true)},{prop:"RemoveTags",name:"RemoveTags",pkg:"",typ:$funcType([AV],[],true)},{prop:"HasTags",name:"HasTags",pkg:"",typ:$funcType([AV],[$Bool],true)},{prop:"Tags",name:"Tags",pkg:"",typ:$funcType([],[AV],false)},{prop:"SetShape",name:"SetShape",pkg:"",typ:$funcType([P],[],false)},{prop:"BoundsToSpace",name:"BoundsToSpace",pkg:"",typ:$funcType([$Float64,$Float64],[$Int,$Int,$Int,$Int],false)},{prop:"SharesCells",name:"SharesCells",pkg:"",typ:$funcType([AN],[$Bool],false)},{prop:"SharesCellsTags",name:"SharesCellsTags",pkg:"",typ:$funcType([AV],[$Bool],true)},{prop:"Center",name:"Center",pkg:"",typ:$funcType([],[$Float64,$Float64],false)},{prop:"SetCenter",name:"SetCenter",pkg:"",typ:$funcType([$Float64,$Float64],[],false)},{prop:"CellPosition",name:"CellPosition",pkg:"",typ:$funcType([],[$Int,$Int],false)},{prop:"SetRight",name:"SetRight",pkg:"",typ:$funcType([$Float64],[],false)},{prop:"SetBottom",name:"SetBottom",pkg:"",typ:$funcType([$Float64],[],false)},{prop:"Bottom",name:"Bottom",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"Right",name:"Right",pkg:"",typ:$funcType([],[$Float64],false)},{prop:"SetBounds",name:"SetBounds",pkg:"",typ:$funcType([B,B],[],false)},{prop:"Check",name:"Check",pkg:"",typ:$funcType([$Float64,$Float64,AV],[AW],true)},{prop:"Overlaps",name:"Overlaps",pkg:"",typ:$funcType([AN],[$Bool],false)},{prop:"AddToIgnoreList",name:"AddToIgnoreList",pkg:"",typ:$funcType([AN],[],false)},{prop:"RemoveFromIgnoreList",name:"RemoveFromIgnoreList",pkg:"",typ:$funcType([AN],[],false)}];AW.methods=[{prop:"HasTags",name:"HasTags",pkg:"",typ:$funcType([AV],[$Bool],true)},{prop:"ObjectsByTags",name:"ObjectsByTags",pkg:"",typ:$funcType([AV],[AO],true)},{prop:"ContactWithObject",name:"ContactWithObject",pkg:"",typ:$funcType([AN],[B],false)},{prop:"ContactWithCell",name:"ContactWithCell",pkg:"",typ:$funcType([AJ],[B],false)},{prop:"SlideAgainstCell",name:"SlideAgainstCell",pkg:"",typ:$funcType([AJ,AV],[B],true)}];AJ.methods=[{prop:"register",name:"register",pkg:"resolv",typ:$funcType([AN],[],false)},{prop:"unregister",name:"unregister",pkg:"resolv",typ:$funcType([AN],[],false)},{prop:"Contains",name:"Contains",pkg:"",typ:$funcType([AN],[$Bool],false)},{prop:"ContainsTags",name:"ContainsTags",pkg:"",typ:$funcType([AV],[$Bool],true)},{prop:"Occupied",name:"Occupied",pkg:"",typ:$funcType([],[$Bool],false)}];B.init($Float64);N.init("",[{prop:"Cells",name:"Cells",embedded:false,exported:true,typ:AL,tag:""},{prop:"CellWidth",name:"CellWidth",embedded:false,exported:true,typ:$Int,tag:""},{prop:"CellHeight",name:"CellHeight",embedded:false,exported:true,typ:$Int,tag:""}]);P.init([{prop:"Bounds",name:"Bounds",pkg:"",typ:$funcType([],[B,B],false)},{prop:"Clone",name:"Clone",pkg:"",typ:$funcType([],[P],false)},{prop:"Intersection",name:"Intersection",pkg:"",typ:$funcType([$Float64,$Float64,P],[AU],false)},{prop:"Position",name:"Position",pkg:"",typ:$funcType([],[$Float64,$Float64],false)},{prop:"SetPosition",name:"SetPosition",pkg:"",typ:$funcType([$Float64,$Float64],[],false)}]);Q.init("",[{prop:"Start",name:"Start",embedded:false,exported:true,typ:B,tag:""},{prop:"End",name:"End",embedded:false,exported:true,typ:B,tag:""}]);S.init("",[{prop:"Points",name:"Points",embedded:false,exported:true,typ:AP,tag:""},{prop:"X",name:"X",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Y",name:"Y",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Closed",name:"Closed",embedded:false,exported:true,typ:$Bool,tag:""}]);U.init("",[{prop:"Points",name:"Points",embedded:false,exported:true,typ:AP,tag:""},{prop:"MTV",name:"MTV",embedded:false,exported:true,typ:B,tag:""},{prop:"Center",name:"Center",embedded:false,exported:true,typ:B,tag:""}]);X.init("",[{prop:"X",name:"X",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Y",name:"Y",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Radius",name:"Radius",embedded:false,exported:true,typ:$Float64,tag:""}]);Z.init("",[{prop:"Min",name:"Min",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Max",name:"Max",embedded:false,exported:true,typ:$Float64,tag:""}]);AA.init("resolv",[{prop:"Shape",name:"Shape",embedded:false,exported:true,typ:P,tag:""},{prop:"Space",name:"Space",embedded:false,exported:true,typ:AM,tag:""},{prop:"X",name:"X",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Y",name:"Y",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"W",name:"W",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"H",name:"H",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"TouchingCells",name:"TouchingCells",embedded:false,exported:true,typ:AK,tag:""},{prop:"Data",name:"Data",embedded:false,exported:true,typ:$emptyInterface,tag:""},{prop:"ignoreList",name:"ignoreList",embedded:false,exported:false,typ:AY,tag:""},{prop:"tags",name:"tags",embedded:false,exported:false,typ:AV,tag:""}]);AE.init("resolv",[{prop:"checkingObject",name:"checkingObject",embedded:false,exported:false,typ:AN,tag:""},{prop:"dx",name:"dx",embedded:false,exported:false,typ:$Float64,tag:""},{prop:"dy",name:"dy",embedded:false,exported:false,typ:$Float64,tag:""},{prop:"Objects",name:"Objects",embedded:false,exported:true,typ:AO,tag:""},{prop:"Cells",name:"Cells",embedded:false,exported:true,typ:AK,tag:""}]);AG.init("",[{prop:"X",name:"X",embedded:false,exported:true,typ:$Int,tag:""},{prop:"Y",name:"Y",embedded:false,exported:true,typ:$Int,tag:""},{prop:"Objects",name:"Objects",embedded:false,exported:true,typ:AO,tag:""}]);$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:$r=A.$init();$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["jsexport/battle"]=(function(){var $pkg={},$init,A,B,C,D,E,F,H,I,J,K,L,M,N,S,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,AZ,BA,BB,BC,BD,BE,BF,BG,BH,BI,P,O,Q,R,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH,AI;A=$packages["math"];B=$packages["resolv"];C=$pkg.Vec2D=$newType(0,$kindStruct,"battle.Vec2D",true,"jsexport/battle",true,function(X_,Y_){this.$val=this;if(arguments.length===0){this.X=0;this.Y=0;return;}this.X=X_;this.Y=Y_;});D=$pkg.Polygon2D=$newType(0,$kindStruct,"battle.Polygon2D",true,"jsexport/battle",true,function(Anchor_,Points_){this.$val=this;if(arguments.length===0){this.Anchor=BE.nil;this.Points=BF.nil;return;}this.Anchor=Anchor_;this.Points=Points_;});E=$pkg.PlayerDownsync=$newType(0,$kindStruct,"battle.PlayerDownsync",true,"jsexport/battle",true,function(Id_,VirtualGridX_,VirtualGridY_,DirX_,DirY_,VelX_,VelY_,Speed_,BattleState_,JoinIndex_,ColliderRadius_,Removed_,Score_,LastMoveGmtMillis_,FramesToRecover_,Hp_,MaxHp_,CharacterState_,InAir_){this.$val=this;if(arguments.length===0){this.Id=0;this.VirtualGridX=0;this.VirtualGridY=0;this.DirX=0;this.DirY=0;this.VelX=0;this.VelY=0;this.Speed=0;this.BattleState=0;this.JoinIndex=0;this.ColliderRadius=0;this.Removed=false;this.Score=0;this.LastMoveGmtMillis=0;this.FramesToRecover=0;this.Hp=0;this.MaxHp=0;this.CharacterState=0;this.InAir=false;return;}this.Id=Id_;this.VirtualGridX=VirtualGridX_;this.VirtualGridY=VirtualGridY_;this.DirX=DirX_;this.DirY=DirY_;this.VelX=VelX_;this.VelY=VelY_;this.Speed=Speed_;this.BattleState=BattleState_;this.JoinIndex=JoinIndex_;this.ColliderRadius=ColliderRadius_;this.Removed=Removed_;this.Score=Score_;this.LastMoveGmtMillis=LastMoveGmtMillis_;this.FramesToRecover=FramesToRecover_;this.Hp=Hp_;this.MaxHp=MaxHp_;this.CharacterState=CharacterState_;this.InAir=InAir_;});F=$pkg.InputFrameDecoded=$newType(0,$kindStruct,"battle.InputFrameDecoded",true,"jsexport/battle",true,function(Dx_,Dy_,BtnALevel_,BtnBLevel_){this.$val=this;if(arguments.length===0){this.Dx=0;this.Dy=0;this.BtnALevel=0;this.BtnBLevel=0;return;}this.Dx=Dx_;this.Dy=Dy_;this.BtnALevel=BtnALevel_;this.BtnBLevel=BtnBLevel_;});H=$pkg.Barrier=$newType(0,$kindStruct,"battle.Barrier",true,"jsexport/battle",true,function(Boundary_){this.$val=this;if(arguments.length===0){this.Boundary=BG.nil;return;}this.Boundary=Boundary_;});I=$pkg.Bullet=$newType(0,$kindStruct,"battle.Bullet",true,"jsexport/battle",true,function(BattleLocalId_,StartupFrames_,ActiveFrames_,RecoveryFrames_,RecoveryFramesOnBlock_,RecoveryFramesOnHit_,HitboxOffset_,OriginatedRenderFrameId_,HitStunFrames_,BlockStunFrames_,Pushback_,ReleaseTriggerType_,Damage_,OffenderJoinIndex_,OffenderPlayerId_,SelfMoveforwardX_,SelfMoveforwardY_,HitboxSizeX_,HitboxSizeY_){this.$val=this;if(arguments.length===0){this.BattleLocalId=0;this.StartupFrames=0;this.ActiveFrames=0;this.RecoveryFrames=0;this.RecoveryFramesOnBlock=0;this.RecoveryFramesOnHit=0;this.HitboxOffset=0;this.OriginatedRenderFrameId=0;this.HitStunFrames=0;this.BlockStunFrames=0;this.Pushback=0;this.ReleaseTriggerType=0;this.Damage=0;this.OffenderJoinIndex=0;this.OffenderPlayerId=0;this.SelfMoveforwardX=0;this.SelfMoveforwardY=0;this.HitboxSizeX=0;this.HitboxSizeY=0;return;}this.BattleLocalId=BattleLocalId_;this.StartupFrames=StartupFrames_;this.ActiveFrames=ActiveFrames_;this.RecoveryFrames=RecoveryFrames_;this.RecoveryFramesOnBlock=RecoveryFramesOnBlock_;this.RecoveryFramesOnHit=RecoveryFramesOnHit_;this.HitboxOffset=HitboxOffset_;this.OriginatedRenderFrameId=OriginatedRenderFrameId_;this.HitStunFrames=HitStunFrames_;this.BlockStunFrames=BlockStunFrames_;this.Pushback=Pushback_;this.ReleaseTriggerType=ReleaseTriggerType_;this.Damage=Damage_;this.OffenderJoinIndex=OffenderJoinIndex_;this.OffenderPlayerId=OffenderPlayerId_;this.SelfMoveforwardX=SelfMoveforwardX_;this.SelfMoveforwardY=SelfMoveforwardY_;this.HitboxSizeX=HitboxSizeX_;this.HitboxSizeY=HitboxSizeY_;});J=$pkg.MeleeBullet=$newType(0,$kindStruct,"battle.MeleeBullet",true,"jsexport/battle",true,function(Bullet_){this.$val=this;if(arguments.length===0){this.Bullet=new I.ptr(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return;}this.Bullet=Bullet_;});K=$pkg.FireballBullet=$newType(0,$kindStruct,"battle.FireballBullet",true,"jsexport/battle",true,function(VirtualGridX_,VirtualGridY_,DirX_,DirY_,VelX_,VelY_,Speed_,Bullet_){this.$val=this;if(arguments.length===0){this.VirtualGridX=0;this.VirtualGridY=0;this.DirX=0;this.DirY=0;this.VelX=0;this.VelY=0;this.Speed=0;this.Bullet=new I.ptr(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);return;}this.VirtualGridX=VirtualGridX_;this.VirtualGridY=VirtualGridY_;this.DirX=DirX_;this.DirY=DirY_;this.VelX=VelX_;this.VelY=VelY_;this.Speed=Speed_;this.Bullet=Bullet_;});L=$pkg.RoomDownsyncFrame=$newType(0,$kindStruct,"battle.RoomDownsyncFrame",true,"jsexport/battle",true,function(Id_,PlayersArr_,CountdownNanos_,MeleeBullets_,FireballBullets_,BackendUnconfirmedMask_,ShouldForceResync_,PlayerOpPatternToSkillId_){this.$val=this;if(arguments.length===0){this.Id=0;this.PlayersArr=AW.nil;this.CountdownNanos=new $Int64(0,0);this.MeleeBullets=AX.nil;this.FireballBullets=BC.nil;this.BackendUnconfirmedMask=new $Uint64(0,0);this.ShouldForceResync=false;this.PlayerOpPatternToSkillId=false;return;}this.Id=Id_;this.PlayersArr=PlayersArr_;this.CountdownNanos=CountdownNanos_;this.MeleeBullets=MeleeBullets_;this.FireballBullets=FireballBullets_;this.BackendUnconfirmedMask=BackendUnconfirmedMask_;this.ShouldForceResync=ShouldForceResync_;this.PlayerOpPatternToSkillId=PlayerOpPatternToSkillId_;});M=$pkg.InputFrameDownsync=$newType(0,$kindStruct,"battle.InputFrameDownsync",true,"jsexport/battle",true,function(InputFrameId_,InputList_,ConfirmedList_){this.$val=this;if(arguments.length===0){this.InputFrameId=0;this.InputList=AV.nil;this.ConfirmedList=new $Uint64(0,0);return;}this.InputFrameId=InputFrameId_;this.InputList=InputList_;this.ConfirmedList=ConfirmedList_;});N=$pkg.RingBuffer=$newType(0,$kindStruct,"battle.RingBuffer",true,"jsexport/battle",true,function(Ed_,St_,EdFrameId_,StFrameId_,N_,Cnt_,Eles_){this.$val=this;if(arguments.length===0){this.Ed=0;this.St=0;this.EdFrameId=0;this.StFrameId=0;this.N=0;this.Cnt=0;this.Eles=AL.nil;return;}this.Ed=Ed_;this.St=St_;this.EdFrameId=EdFrameId_;this.StFrameId=StFrameId_;this.N=N_;this.Cnt=Cnt_;this.Eles=Eles_;});S=$pkg.SatResult=$newType(0,$kindStruct,"battle.SatResult",true,"jsexport/battle",true,function(Overlap_,OverlapX_,OverlapY_,AContainedInB_,BContainedInA_,Axis_){this.$val=this;if(arguments.length===0){this.Overlap=0;this.OverlapX=0;this.OverlapY=0;this.AContainedInB=false;this.BContainedInA=false;this.Axis=B.Vector.nil;return;}this.Overlap=Overlap_;this.OverlapX=OverlapX_;this.OverlapY=OverlapY_;this.AContainedInB=AContainedInB_;this.BContainedInA=BContainedInA_;this.Axis=Axis_;});AJ=$sliceType($Int32);AK=$sliceType(AJ);AL=$sliceType($emptyInterface);AM=$ptrType(S);AN=$sliceType(C);AO=$sliceType($String);AP=$ptrType(B.Collision);AQ=$ptrType(AN);AR=$ptrType(E);AS=$ptrType(J);AT=$ptrType(B.ConvexPolygon);AU=$ptrType(M);AV=$sliceType($Uint64);AW=$sliceType(AR);AX=$sliceType(AS);AY=$sliceType(AQ);AZ=$ptrType(B.Object);BA=$sliceType(AZ);BB=$ptrType(K);BC=$sliceType(BB);BD=$sliceType($Float64);BE=$ptrType(C);BF=$sliceType(BE);BG=$ptrType(D);BH=$mapType($Int,$Int);BI=$ptrType(N);O=function(a){var a;return new N.ptr(0,0,0,0,a,0,$makeSlice(AL,a));};$pkg.NewRingBuffer=O;N.ptr.prototype.Put=function(a){var a,b,c,d;b=this;while(true){if(!(0=b.N)){break;}b.Pop();}(c=b.Eles,d=b.Ed,((d<0||d>=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]=a));b.EdFrameId=b.EdFrameId+(1)>>0;b.Cnt=b.Cnt+(1)>>0;b.Ed=b.Ed+(1)>>0;if(b.Ed>=b.N){b.Ed=b.Ed-(b.N)>>0;}};N.prototype.Put=function(a){return this.$val.Put(a);};N.ptr.prototype.Pop=function(){var a,b,c,d;a=this;if(0===a.Cnt){return $ifaceNil;}d=(b=a.Eles,c=a.St,((c<0||c>=b.$length)?($throwRuntimeError("index out of range"),undefined):b.$array[b.$offset+c]));a.StFrameId=a.StFrameId+(1)>>0;a.Cnt=a.Cnt-(1)>>0;a.St=a.St+(1)>>0;if(a.St>=a.N){a.St=a.St-(a.N)>>0;}return d;};N.prototype.Pop=function(){return this.$val.Pop();};N.ptr.prototype.GetArrIdxByOffset=function(a){var a,b,c;b=this;if((0===b.Cnt)||0>a){return-1;}c=b.St+a>>0;if(b.St=b.N){c=c-(b.N)>>0;}if(c>=b.St||c=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+c]));};N.prototype.GetByOffset=function(a){return this.$val.GetByOffset(a);};N.ptr.prototype.GetByFrameId=function(a){var a,b;b=this;if(a>=b.EdFrameId||a>0);};N.prototype.GetByFrameId=function(a){return this.$val.GetByFrameId(a);};N.ptr.prototype.SetByFrameId=function(a,b){var a,b,c,d,e,f,g,h,i,j,k,l,m,n;c=this;d=c.StFrameId;e=c.EdFrameId;f=d;g=e;if(bb){h=c.GetArrIdxByOffset(b-c.StFrameId>>0);if(!((-1===h))){(i=c.Eles,((h<0||h>=i.$length)?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+h]=a));return[0,f,g];}}j=0;if(g>0))>>$min(c,31))>>0);};$pkg.ConvertToInputFrameId=Q;R=function(a){var a,b,c,d,e,f,g,h;b=new $Uint64(a.$high&0,(a.$low&15)>>>0);d=(((c=$shiftRightUint64(a,4),new $Uint64(c.$high&0,(c.$low&1)>>>0)).$low>>0));f=(((e=$shiftRightUint64(a,5),new $Uint64(e.$high&0,(e.$low&1)>>>0)).$low>>0));return new F.ptr((g=(($flatten64(b)<0||$flatten64(b)>=$pkg.DIRECTION_DECODER.$length)?($throwRuntimeError("index out of range"),undefined):$pkg.DIRECTION_DECODER.$array[$pkg.DIRECTION_DECODER.$offset+$flatten64(b)]),(0>=g.$length?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+0])),(h=(($flatten64(b)<0||$flatten64(b)>=$pkg.DIRECTION_DECODER.$length)?($throwRuntimeError("index out of range"),undefined):$pkg.DIRECTION_DECODER.$array[$pkg.DIRECTION_DECODER.$offset+$flatten64(b)]),(1>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+1])),d,f);};T=function(a,b,c,d){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,$s,$deferred,$r,$c}=$restore(this,{a,b,c,d});$s=$s||0;var $err=null;try{s:while(true){switch($s){case 0:$deferred=[];$curGoroutine.deferStack.push($deferred);c=[c];e=[e];f=[f];g=c[0].Position();e[0]=g[0];f[0]=g[1];$deferred.push([(function(c,e,f){return function(){c[0].SetPosition(e[0],f[0]);};})(c,e,f),[]]);c[0].SetPosition(e[0]+a,f[0]+b);h=new S.ptr(0,0,0,true,true,new B.Vector([0,0]));i=U(c[0],d,h);if(i){$s=1;continue;}$s=2;continue;case 1:j=h.Overlap*h.OverlapX;k=h.Overlap*h.OverlapY;l=j;m=k;n=[true,l,m,h];$s=4;case 4:return n;case 2:o=[false,0,0,h];$s=5;case 5:return o;case 3:$s=-1;return[false,0,0,AM.nil];}return;}}catch(err){$err=err;$s=-1;return[false,0,0,AM.nil];}finally{$callDeferred($deferred,$err);if($curGoroutine.asleep){var $f={$blk:T,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,$s,$deferred};return $f;}}};$pkg.CalcPushbacks=T;U=function(a,b,c){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;d=a.Points.$length;e=b.Points.$length;f=d;g=e;if((1===f)&&(1===g)){if(!(AM.nil===c)){c.Overlap=0;}return((h=(i=a.Points,(0>=i.$length?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+0])),(0>=h.$length?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+0]))===(j=(k=b.Points,(0>=k.$length?($throwRuntimeError("index out of range"),undefined):k.$array[k.$offset+0])),(0>=j.$length?($throwRuntimeError("index out of range"),undefined):j.$array[j.$offset+0])))&&((l=(m=a.Points,(0>=m.$length?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+0])),(1>=l.$length?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+1]))===(n=(o=b.Points,(0>=o.$length?($throwRuntimeError("index out of range"),undefined):o.$array[o.$offset+0])),(1>=n.$length?($throwRuntimeError("index out of range"),undefined):n.$array[n.$offset+1])));}if(1=p.$length)?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+q]);if(V(a,b,r.Unit(),c)){return false;}q++;}}if(1=s.$length)?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+t]);if(V(a,b,u.Unit(),c)){return false;}t++;}}return true;};V=function(a,b,c,d){var a,aa,ab,ac,ad,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;e=1.7e+308;f=-1.7e+308;g=1.7e+308;h=-1.7e+308;i=e;j=f;k=g;l=h;m=a.Points;n=0;while(true){if(!(n=m.$length)?($throwRuntimeError("index out of range"),undefined):m.$array[m.$offset+n]);p=((0>=o.$length?($throwRuntimeError("index out of range"),undefined):o.$array[o.$offset+0])+a.X)*(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0])+((1>=o.$length?($throwRuntimeError("index out of range"),undefined):o.$array[o.$offset+1])+a.Y)*(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]);if(i>p){i=p;}if(j=q.$length)?($throwRuntimeError("index out of range"),undefined):q.$array[q.$offset+r]);t=((0>=s.$length?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+0])+b.X)*(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0])+((1>=s.$length?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+1])+b.Y)*(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1]);if(k>t){k=t;}if(ll||jl){u=i-l;d.AContainedInB=false;}else{x=j-k;y=l-i;if(x=ab.$length?($throwRuntimeError("index out of range"),undefined):ab.$array[ab.$offset+0])))&&(0===(ac=d.Axis,(1>=ac.$length?($throwRuntimeError("index out of range"),undefined):ac.$array[ac.$offset+1]))))||z>aa){ad=1;if(u<0){ad=-1;}d.Overlap=aa;d.OverlapX=(0>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+0])*ad;d.OverlapY=(1>=c.$length?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+1])*ad;}d.Axis=c;}return false;};W=function(a,b,c){var a,b,c,d,e;d=((A.Floor(a*c)>>0));e=((A.Floor(b*c)>>0));return[d,e];};$pkg.WorldToVirtualGridPos=W;X=function(a,b,c){var a,b,c,d,e;d=(a)*c;e=(b)*c;return[d,e];};$pkg.VirtualGridToWorldPos=X;Y=function(a,b,c,d,e,f,g,h,i,j){var a,b,c,d,e,f,g,h,i,j;return[a-c-g+i,b-d-f+j];};$pkg.WorldToPolygonColliderBLPos=Y;Z=function(a,b,c,d,e,f,g,h,i,j){var a,b,c,d,e,f,g,h,i,j;return[a+c+g-i,b+d+f-j];};$pkg.PolygonColliderBLToWorldPos=Z;AA=function(a,b,c,d,e,f,g,h,i,j,k){var a,b,c,d,e,f,g,h,i,j,k,l,m,n;l=Z(a,b,c,d,e,f,g,h,i,j);m=l[0];n=l[1];return W(m,n,k);};$pkg.PolygonColliderBLToVirtualGridPos=AA;AB=function(a,b,c,d,e,f,g,h,i,j,k){var a,b,c,d,e,f,g,h,i,j,k,l,m,n;l=X(a,b,k);m=l[0];n=l[1];return Y(m,n,c,d,e,f,g,h,i,j);};$pkg.VirtualGridToPolygonColliderBLPos=AB;AC=function(a,b,c,d,e){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,$s,$r,$c}=$restore(this,{a,b,c,d,e});$s=$s||0;s:while(true){switch($s){case 0:f=[f];f[0]=$makeSlice(AN,0,10);g=b.Check(0,0,new AO([]));if(AP.nil===g){$s=-1;return(f.$ptr||(f.$ptr=new AQ(function(){return this.$target[0];},function($v){this.$target[0]=$v;},f)));}h=g.Objects;i=0;case 1:if(!(i=h.$length)?($throwRuntimeError("index out of range"),undefined):h.$array[h.$offset+i]);k=false;l=j.Data;if($assertType(l,AR,true)[1]){}else if($assertType(l,AS,true)[1]){}else{k=true;}if(!k){i++;$s=1;continue;}m=$assertType(j.Shape,AT);o=T(0,0,c,m);$s=3;case 3:if($c){$c=false;o=o.$blk();}if(o&&o.$blk!==undefined){break s;}n=o;p=n[0];q=n[1];r=n[2];s=n[3];if(!p){i++;$s=1;continue;}t=(s.Overlap-d)*s.OverlapX;u=(s.Overlap-d)*s.OverlapY;q=t;r=u;f[0]=$append(f[0],new C.ptr(s.OverlapX,s.OverlapY));e.X=e.X+(q);e.Y=e.Y+(r);i++;$s=1;continue;case 2:$s=-1;return(f.$ptr||(f.$ptr=new AQ(function(){return this.$target[0];},function($v){this.$target[0]=$v;},f)));}return;}var $f={$blk:AC,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,$s};return $f;};AD=function(a,b,c,d,e,f){var a,aa,ab,ac,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;g=Q(c.Id,e,f);h=Q(c.Id-1>>0,e,f);if(0>=g){return[-2,false,0,0];}i=$assertType(d.GetByFrameId(g),AU).InputList;j=AV.nil;if(0>0,((m<0||m>=i.$length)?($throwRuntimeError("index out of range"),undefined):i.$array[i.$offset+m])));o=n.Dx;p=n.Dy;q=o;r=p;s=0;t=0;u=s;v=t;if(!(AV.nil===j)){x=R((w=l-1>>0,((w<0||w>=j.$length)?($throwRuntimeError("index out of range"),undefined):j.$array[j.$offset+w])));u=x.BtnALevel;v=x.BtnBLevel;}if(n.BtnBLevel>v){y=false;if((4===a.CharacterState)||(5===a.CharacterState)||(6===a.CharacterState)){y=true;}z=false;if((0===a.CharacterState)||(1===a.CharacterState)||(4===a.CharacterState)){z=true;}if(!y&&z){k=true;}}aa=-1;if(n.BtnALevel>u){aa=0;ab=0;ac=0;q=ab;r=ac;}return[aa,k,q,r];};AE=function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){var{a,aa,ab,ac,ad,ae,af,ag,ah,ai,aj,ak,al,am,an,ao,ap,aq,ar,as,at,au,av,aw,ax,ay,az,b,ba,bb,bc,bd,be,bf,bg,bh,bi,bj,bk,bl,bm,bn,bo,bp,bq,br,bs,bt,bu,bv,bw,bx,by,bz,c,ca,cb,cc,cd,ce,cf,cg,ch,ci,cj,ck,cl,cm,cn,co,cp,cq,cr,cs,ct,cu,cv,cw,cx,cy,cz,d,da,db,dc,dd,de,df,dg,dh,di,dj,dk,dl,dm,dn,dp,dq,dr,ds,dt,du,dv,dw,dx,dy,dz,e,ea,eb,ec,ed,ee,ef,eg,eh,ei,ej,ek,el,em,en,eo,ep,eq,er,es,et,eu,ev,ew,ex,ey,ez,f,fa,fb,fc,fd,fe,ff,fg,fh,fi,fj,fk,fl,fm,fn,fo,fp,fq,fr,fs,ft,fu,fv,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,$s,$r,$c}=$restore(this,{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p});$s=$s||0;s:while(true){switch($s){case 0:q=b.PlayersArr.$length;r=$makeSlice(AW,q);s=b.PlayersArr;t=0;while(true){if(!(t=s.$length)?($throwRuntimeError("index out of range"),undefined):s.$array[s.$offset+t]);((u<0||u>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+u]=new E.ptr(v.Id,v.VirtualGridX,v.VirtualGridY,v.DirX,v.DirY,v.VelX,v.VelY,v.Speed,v.BattleState,v.JoinIndex,0,v.Removed,v.Score,0,v.FramesToRecover-1>>0,v.Hp,v.MaxHp,v.CharacterState,true));if(((u<0||u>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+u]).FramesToRecover<0){((u<0||u>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+u]).FramesToRecover=0;}t++;}w=$makeSlice(AX,0,b.MeleeBullets.$length);x=$makeSlice(AN,q);y=$makeSlice(AY,q);z=b.PlayersArr;aa=0;while(true){if(!(aa=z.$length)?($throwRuntimeError("index out of range"),undefined):z.$array[z.$offset+aa]);ae=((ac<0||ac>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+ac]);af=AD(ad,ae,b,a,h,i);ag=af[0];ah=af[1];ai=af[2];aj=af[3];if(-2===ag){aa++;continue;}if(ah){ae.VelY=g;ae.VirtualGridY=ae.VirtualGridY+(g)>>0;}ak=ad.JoinIndex;if(!((-1===ag))){al=(am=p[$Int.keyFor(((((ak>>0))<<8>>0))+ag>>0)],am!==undefined?[am.v,true]:[0,false]);an=al[0];ao=al[1];if(ao){aq=$assertType((ap=P[$Int.keyFor(an)],ap!==undefined?ap.v:$ifaceNil),AS);ab[0]=$clone(aq,J);ab[0].Bullet.OffenderJoinIndex=ak;ab[0].Bullet.OffenderPlayerId=ad.Id;ab[0].Bullet.OriginatedRenderFrameId=b.Id;w=$append(w,ab[0]);ae.FramesToRecover=ab[0].Bullet.RecoveryFrames;ae.CharacterState=2;if(false===ad.InAir){ae.VelX=0;}}aa++;continue;}if(!((0===ai))||!((0===aj))){ar=ai;as=aj;ae.DirX=ar;ae.DirY=as;ae.VelX=$imul(ai,ad.Speed);ae.CharacterState=1;}else{ae.CharacterState=0;ae.VelX=0;}aa++;}at=b.PlayersArr;au=0;case 1:if(!(au=at.$length)?($throwRuntimeError("index out of range"),undefined):at.$array[at.$offset+au]);ax=aw.JoinIndex;ay=0;az=0;(ba=ax-1>>0,((ba<0||ba>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+ba])).X=ay;(bb=ax-1>>0,((bb<0||bb>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+bb])).Y=az;bc=131072+ax>>0;be=(bd=d[$Int32.keyFor(bc)],bd!==undefined?bd.v:AZ.nil);bf=((av<0||av>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+av]);bg=aw.VirtualGridX+aw.VelX>>0;bh=aw.VirtualGridY+aw.VelY>>0;bi=bg;bj=bh;bk=AB(bi,bj,be.W*0.5,be.H*0.5,0,0,0,0,j,k,o);be.X=bk[0];be.Y=bk[1];$r=be.Update();$s=3;case 3:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}if(aw.InAir){bf.VelX=bf.VelX+(e)>>0;bf.VelY=bf.VelY+(f)>>0;}au++;$s=1;continue;case 2:bl=$makeSlice(BA,0,b.MeleeBullets.$length);bm=b.MeleeBullets;bn=0;case 4:if(!(bn=bm.$length)?($throwRuntimeError("index out of range"),undefined):bm.$array[bm.$offset+bn]);if(((bo.Bullet.OriginatedRenderFrameId+bo.Bullet.StartupFrames>>0)<=b.Id)&&(((bo.Bullet.OriginatedRenderFrameId+bo.Bullet.StartupFrames>>0)+bo.Bullet.ActiveFrames>>0)>b.Id)){$s=6;continue;}$s=7;continue;case 6:br=(bp=b.PlayersArr,bq=bo.Bullet.OffenderJoinIndex-1>>0,((bq<0||bq>=bp.$length)?($throwRuntimeError("index out of range"),undefined):bp.$array[bp.$offset+bq]));bs=1;if(0>br.DirX){bs=-1;}bt=X(br.VirtualGridX,br.VirtualGridY,o);bu=bt[0];bv=bt[1];bw=bu+bs*bo.Bullet.HitboxOffset;bx=bv;by=bw;bz=bx;ca=AF(by,bz,bo.Bullet.HitboxSizeX,bo.Bullet.HitboxSizeY,l,l,l,l,j,k,bo,"MeleeBullet");$s=9;case 9:if($c){$c=false;ca=ca.$blk();}if(ca&&ca.$blk!==undefined){break s;}cb=ca;$r=c.Add(new BA([cb]));$s=10;case 10:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}bl=$append(bl,cb);$s=8;continue;case 7:w=$append(w,bo);case 8:bn++;$s=4;continue;case 5:cc=b.PlayersArr;cd=0;case 11:if(!(cd=cc.$length)?($throwRuntimeError("index out of range"),undefined):cc.$array[cc.$offset+cd]);cg=cf.JoinIndex;ch=131072+cg>>0;cj=(ci=d[$Int32.keyFor(ch)],ci!==undefined?ci.v:AZ.nil);ck=$assertType(cj.Shape,AT);cm=AC(cg,cj,ck,l,(cl=cg-1>>0,((cl<0||cl>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+cl])));$s=13;case 13:if($c){$c=false;cm=cm.$blk();}if(cm&&cm.$blk!==undefined){break s;}(cn=cg-1>>0,((cn<0||cn>=y.$length)?($throwRuntimeError("index out of range"),undefined):y.$array[y.$offset+cn]=cm));co=((ce<0||ce>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+ce]);cp=false;cq=cj.Check(0,0,new AO([]));if(!(AP.nil===cq)){$s=14;continue;}$s=15;continue;case 14:cr=cq.Objects;cs=0;case 16:if(!(cs=cr.$length)?($throwRuntimeError("index out of range"),undefined):cr.$array[cr.$offset+cs]);cu=false;cv=false;cw=false;cx=cu;cy=cv;cz=cw;da=ct.Data;if($assertType(da,AR,true)[1]){cy=true;}else if($assertType(da,AS,true)[1]){cz=true;}else{cx=true;}if(cz){cs++;$s=16;continue;}db=$assertType(ct.Shape,AT);dd=T(0,0,ck,db);$s=18;case 18:if($c){$c=false;dd=dd.$blk();}if(dd&&dd.$blk!==undefined){break s;}dc=dd;de=dc[0];df=dc[1];dg=dc[2];dh=dc[3];if(!de){cs++;$s=16;continue;}di=dh.OverlapX*0+dh.OverlapY*-1;if(cy){dj=(dh.Overlap-l*2)*dh.OverlapX;dk=(dh.Overlap-l*2)*dh.OverlapY;df=dj;dg=dk;}dl=(dm=cg-1>>0,((dm<0||dm>=y.$length)?($throwRuntimeError("index out of range"),undefined):y.$array[y.$offset+dm])).$get();dn=0;while(true){if(!(dn=dl.$length)?($throwRuntimeError("index out of range"),undefined):dl.$array[dl.$offset+dn]),C);dq=df*dp.X+dg*dp.Y;if(cx||(cy&&0>dq)){df=df-(dq*dp.X);dg=dg-(dq*dp.Y);}dn++;}dr=cg-1>>0;((dr<0||dr>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+dr]).X=((dr<0||dr>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+dr]).X+(df);ds=cg-1>>0;((ds<0||ds>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+ds]).Y=((ds<0||ds>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+ds]).Y+(dg);if(m=cf.VelY){co.VelX=0;co.VelY=0;co.CharacterState=0;co.FramesToRecover=0;}}if(cf.InAir){dt=co.CharacterState;du=dt;if((du===(0))||(du===(1))){co.CharacterState=4;}else if(du===(2)){co.CharacterState=5;}else if(du===(3)){co.CharacterState=6;}}cd++;$s=11;continue;case 12:dv=bl;dw=0;case 19:if(!(dw=dv.$length)?($throwRuntimeError("index out of range"),undefined):dv.$array[dv.$offset+dw]);dy=$assertType(dx.Data,AS);dz=$assertType(dx.Shape,AT);ea=dx.Check(0,0,new AO([]));dx.Space.Remove(new BA([dx]));if(AP.nil===ea){w=$append(w,dy);dw++;$s=19;continue;}ed=(eb=b.PlayersArr,ec=dy.Bullet.OffenderJoinIndex-1>>0,((ec<0||ec>=eb.$length)?($throwRuntimeError("index out of range"),undefined):eb.$array[eb.$offset+ec]));ee=ea.Objects;ef=0;case 21:if(!(ef=ee.$length)?($throwRuntimeError("index out of range"),undefined):ee.$array[ee.$offset+ef]);eh=$assertType(eg.Shape,AT);ei=eg.Data;if($assertType(ei,AR,true)[1]){$s=23;continue;}$s=24;continue;case 23:ej=ei.$val;if(dy.Bullet.OffenderPlayerId===ej.Id){ef++;$s=21;continue;}em=T(0,0,dz,eh);$s=26;case 26:if($c){$c=false;em=em.$blk();}if(em&&em.$blk!==undefined){break s;}el=em;en=el[0];if(!en){ef++;$s=21;continue;}eo=ej.JoinIndex;ep=1;if(0>ed.DirX){ep=-1;}eq=-ep*dy.Bullet.Pushback;er=0;es=eq;et=er;eu=(ev=eo-1>>0,((ev<0||ev>=y.$length)?($throwRuntimeError("index out of range"),undefined):y.$array[y.$offset+ev])).$get();ew=0;while(true){if(!(ew=eu.$length)?($throwRuntimeError("index out of range"),undefined):eu.$array[eu.$offset+ew]),C);ey=es*ex.X+et*ex.Y;if(0>ey){es=es-(ey*ex.X);et=et-(ey*ex.Y);}ew++;}ez=eo-1>>0;((ez<0||ez>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+ez]).X=((ez<0||ez>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+ez]).X+(es);fa=eo-1>>0;((fa<0||fa>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+fa]).Y=((fa<0||fa>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+fa]).Y+(et);fb=(fc=b.PlayersArr,fd=ej.JoinIndex-1>>0,((fd<0||fd>=fc.$length)?($throwRuntimeError("index out of range"),undefined):fc.$array[fc.$offset+fd]));fe=(ff=ej.JoinIndex-1>>0,((ff<0||ff>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+ff]));fg=fb;fh=fe;fh.CharacterState=3;if(fg.InAir){fh.CharacterState=6;}fj=(fi=ej.JoinIndex-1>>0,((fi<0||fi>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+fi])).FramesToRecover;if(dy.Bullet.HitStunFrames>fj){fh.FramesToRecover=dy.Bullet.HitStunFrames;}$s=25;continue;case 24:ek=ei;case 25:ef++;$s=21;continue;case 22:dw++;$s=19;continue;case 20:fk=b.PlayersArr;fl=0;while(true){if(!(fl=fk.$length)?($throwRuntimeError("index out of range"),undefined):fk.$array[fk.$offset+fl]);fo=fn.JoinIndex;fp=131072+fo>>0;fr=(fq=d[$Int32.keyFor(fp)],fq!==undefined?fq.v:AZ.nil);fs=((fm<0||fm>=r.$length)?($throwRuntimeError("index out of range"),undefined):r.$array[r.$offset+fm]);ft=AA(fr.X-(fu=fo-1>>0,((fu<0||fu>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+fu])).X,fr.Y-(fv=fo-1>>0,((fv<0||fv>=x.$length)?($throwRuntimeError("index out of range"),undefined):x.$array[x.$offset+fv])).Y,fr.W*0.5,fr.H*0.5,0,0,0,0,j,k,n);fs.VirtualGridX=ft[0];fs.VirtualGridY=ft[1];fl++;}$s=-1;return new L.ptr(b.Id+1>>0,r,new $Int64(0,0),w,BC.nil,new $Uint64(0,0),false,false);}return;}var $f={$blk:AE,$c:true,$r,a,aa,ab,ac,ad,ae,af,ag,ah,ai,aj,ak,al,am,an,ao,ap,aq,ar,as,at,au,av,aw,ax,ay,az,b,ba,bb,bc,bd,be,bf,bg,bh,bi,bj,bk,bl,bm,bn,bo,bp,bq,br,bs,bt,bu,bv,bw,bx,by,bz,c,ca,cb,cc,cd,ce,cf,cg,ch,ci,cj,ck,cl,cm,cn,co,cp,cq,cr,cs,ct,cu,cv,cw,cx,cy,cz,d,da,db,dc,dd,de,df,dg,dh,di,dj,dk,dl,dm,dn,dp,dq,dr,ds,dt,du,dv,dw,dx,dy,dz,e,ea,eb,ec,ed,ee,ef,eg,eh,ei,ej,ek,el,em,en,eo,ep,eq,er,es,et,eu,ev,ew,ex,ey,ez,f,fa,fb,fc,fd,fe,ff,fg,fh,fi,fj,fk,fl,fm,fn,fo,fp,fq,fr,fs,ft,fu,fv,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,$s};return $f;};$pkg.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame=AE;AF=function(a,b,c,d,e,f,g,h,i,j,k,l){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,$s,$r,$c}=$restore(this,{a,b,c,d,e,f,g,h,i,j,k,l});$s=$s||0;s:while(true){switch($s){case 0:m=Y(a,b,c*0.5,d*0.5,e,f,g,h,i,j);n=m[0];o=m[1];p=AG(n,o,g+c+h,f+d+e,k,l);$s=1;case 1:if($c){$c=false;p=p.$blk();}if(p&&p.$blk!==undefined){break s;}q=p;$s=2;case 2:return q;}return;}var $f={$blk:AF,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,$s};return $f;};$pkg.GenerateRectCollider=AF;AG=function(a,b,c,d,e,f){var{a,b,c,d,e,f,g,h,$s,$r,$c}=$restore(this,{a,b,c,d,e,f});$s=$s||0;s:while(true){switch($s){case 0:g=B.NewObject(a,b,c,d,new AO([f]));h=B.NewRectangle(0,0,c,d);$r=g.SetShape(h);$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}g.Data=e;$s=-1;return g;}return;}var $f={$blk:AG,$c:true,$r,a,b,c,d,e,f,g,h,$s};return $f;};AH=function(a,b,c,d,e){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,$s,$r,$c}=$restore(this,{a,b,c,d,e});$s=$s||0;s:while(true){switch($s){case 0:f=AI(a);g=0;h=0;i=g;j=h;k=B.NewConvexPolygon(BD.nil);l=f.Points;m=0;while(true){if(!(m=l.$length)?($throwRuntimeError("index out of range"),undefined):l.$array[l.$offset+m]);p=f.Points;q=0;while(true){if(!(q=p.$length)?($throwRuntimeError("index out of range"),undefined):p.$array[p.$offset+q]);if(n===r){q++;continue;}if(A.Abs(s.X-o.X)>i){i=A.Abs(s.X-o.X);}if(A.Abs(s.Y-o.Y)>j){j=A.Abs(s.Y-o.Y);}q++;}m++;}t=0;while(true){if(!(t=u.$length)?($throwRuntimeError("index out of range"),undefined):u.$array[u.$offset+t]));k.AddPoints(new BD([v.X,v.Y]));t=t+(1)>>0;}w=B.NewObject(f.Anchor.X+b,f.Anchor.Y+c,i,j,new AO([e]));$r=w.SetShape(k);$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}w.Data=d;$s=-1;return w;}return;}var $f={$blk:AH,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,$s};return $f;};$pkg.GenerateConvexPolygonCollider=AH;AI=function(a){var a,b,c,d,e,f,g,h,i,j,k;b=new C.ptr(1.7e+308,1.7e+308);c=a.Points;d=0;while(true){if(!(d=c.$length)?($throwRuntimeError("index out of range"),undefined):c.$array[c.$offset+d]);if(e.X=g.$length)?($throwRuntimeError("index out of range"),undefined):g.$array[g.$offset+h]);(k=f.Points,((i<0||i>=k.$length)?($throwRuntimeError("index out of range"),undefined):k.$array[k.$offset+i]=new C.ptr(j.X-b.X,j.Y-b.Y)));h++;}return f;};$pkg.AlignPolygon2DToBoundingBox=AI;BI.methods=[{prop:"Put",name:"Put",pkg:"",typ:$funcType([$emptyInterface],[],false)},{prop:"Pop",name:"Pop",pkg:"",typ:$funcType([],[$emptyInterface],false)},{prop:"GetArrIdxByOffset",name:"GetArrIdxByOffset",pkg:"",typ:$funcType([$Int32],[$Int32],false)},{prop:"GetByOffset",name:"GetByOffset",pkg:"",typ:$funcType([$Int32],[$emptyInterface],false)},{prop:"GetByFrameId",name:"GetByFrameId",pkg:"",typ:$funcType([$Int32],[$emptyInterface],false)},{prop:"SetByFrameId",name:"SetByFrameId",pkg:"",typ:$funcType([$emptyInterface,$Int32],[$Int32,$Int32,$Int32],false)}];C.init("",[{prop:"X",name:"X",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Y",name:"Y",embedded:false,exported:true,typ:$Float64,tag:""}]);D.init("",[{prop:"Anchor",name:"Anchor",embedded:false,exported:true,typ:BE,tag:""},{prop:"Points",name:"Points",embedded:false,exported:true,typ:BF,tag:""}]);E.init("",[{prop:"Id",name:"Id",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VirtualGridX",name:"VirtualGridX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VirtualGridY",name:"VirtualGridY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"DirX",name:"DirX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"DirY",name:"DirY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VelX",name:"VelX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VelY",name:"VelY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Speed",name:"Speed",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"BattleState",name:"BattleState",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"JoinIndex",name:"JoinIndex",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"ColliderRadius",name:"ColliderRadius",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"Removed",name:"Removed",embedded:false,exported:true,typ:$Bool,tag:""},{prop:"Score",name:"Score",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"LastMoveGmtMillis",name:"LastMoveGmtMillis",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"FramesToRecover",name:"FramesToRecover",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Hp",name:"Hp",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"MaxHp",name:"MaxHp",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"CharacterState",name:"CharacterState",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"InAir",name:"InAir",embedded:false,exported:true,typ:$Bool,tag:""}]);F.init("",[{prop:"Dx",name:"Dx",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Dy",name:"Dy",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"BtnALevel",name:"BtnALevel",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"BtnBLevel",name:"BtnBLevel",embedded:false,exported:true,typ:$Int32,tag:""}]);H.init("",[{prop:"Boundary",name:"Boundary",embedded:false,exported:true,typ:BG,tag:""}]);I.init("",[{prop:"BattleLocalId",name:"BattleLocalId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"StartupFrames",name:"StartupFrames",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"ActiveFrames",name:"ActiveFrames",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"RecoveryFrames",name:"RecoveryFrames",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"RecoveryFramesOnBlock",name:"RecoveryFramesOnBlock",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"RecoveryFramesOnHit",name:"RecoveryFramesOnHit",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"HitboxOffset",name:"HitboxOffset",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"OriginatedRenderFrameId",name:"OriginatedRenderFrameId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"HitStunFrames",name:"HitStunFrames",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"BlockStunFrames",name:"BlockStunFrames",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Pushback",name:"Pushback",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"ReleaseTriggerType",name:"ReleaseTriggerType",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Damage",name:"Damage",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"OffenderJoinIndex",name:"OffenderJoinIndex",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"OffenderPlayerId",name:"OffenderPlayerId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"SelfMoveforwardX",name:"SelfMoveforwardX",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"SelfMoveforwardY",name:"SelfMoveforwardY",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"HitboxSizeX",name:"HitboxSizeX",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"HitboxSizeY",name:"HitboxSizeY",embedded:false,exported:true,typ:$Float64,tag:""}]);J.init("",[{prop:"Bullet",name:"Bullet",embedded:true,exported:true,typ:I,tag:""}]);K.init("",[{prop:"VirtualGridX",name:"VirtualGridX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VirtualGridY",name:"VirtualGridY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"DirX",name:"DirX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"DirY",name:"DirY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VelX",name:"VelX",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"VelY",name:"VelY",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Speed",name:"Speed",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Bullet",name:"Bullet",embedded:true,exported:true,typ:I,tag:""}]);L.init("",[{prop:"Id",name:"Id",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"PlayersArr",name:"PlayersArr",embedded:false,exported:true,typ:AW,tag:""},{prop:"CountdownNanos",name:"CountdownNanos",embedded:false,exported:true,typ:$Int64,tag:""},{prop:"MeleeBullets",name:"MeleeBullets",embedded:false,exported:true,typ:AX,tag:""},{prop:"FireballBullets",name:"FireballBullets",embedded:false,exported:true,typ:BC,tag:""},{prop:"BackendUnconfirmedMask",name:"BackendUnconfirmedMask",embedded:false,exported:true,typ:$Uint64,tag:""},{prop:"ShouldForceResync",name:"ShouldForceResync",embedded:false,exported:true,typ:$Bool,tag:""},{prop:"PlayerOpPatternToSkillId",name:"PlayerOpPatternToSkillId",embedded:false,exported:true,typ:BH,tag:""}]);M.init("",[{prop:"InputFrameId",name:"InputFrameId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"InputList",name:"InputList",embedded:false,exported:true,typ:AV,tag:""},{prop:"ConfirmedList",name:"ConfirmedList",embedded:false,exported:true,typ:$Uint64,tag:""}]);N.init("",[{prop:"Ed",name:"Ed",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"St",name:"St",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"EdFrameId",name:"EdFrameId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"StFrameId",name:"StFrameId",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"N",name:"N",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Cnt",name:"Cnt",embedded:false,exported:true,typ:$Int32,tag:""},{prop:"Eles",name:"Eles",embedded:false,exported:true,typ:AL,tag:""}]);S.init("",[{prop:"Overlap",name:"Overlap",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"OverlapX",name:"OverlapX",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"OverlapY",name:"OverlapY",embedded:false,exported:true,typ:$Float64,tag:""},{prop:"AContainedInB",name:"AContainedInB",embedded:false,exported:true,typ:$Bool,tag:""},{prop:"BContainedInA",name:"BContainedInA",embedded:false,exported:true,typ:$Bool,tag:""},{prop:"Axis",name:"Axis",embedded:false,exported:true,typ:B.Vector,tag:""}]);$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:$r=A.$init();$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}$r=B.$init();$s=2;case 2:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}$pkg.DIRECTION_DECODER=new AK([new AJ([0,0]),new AJ([0,2]),new AJ([0,-2]),new AJ([2,0]),new AJ([-2,0]),new AJ([1,1]),new AJ([-1,-1]),new AJ([1,-1]),new AJ([-1,1])]);P=$makeMap($Int.keyFor,[{k:1,v:new J.ptr(new I.ptr(0,5,10,34,34,34,12,0,18,9,8,1,5,0,0,0,0,24,32))}]);}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$packages["jsexport"]=(function(){var $pkg={},$init,A,B,C,R,S,T,U,V,W,X,Y,Z,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,D,E,F,G,H,I,J,K,L,M,N,O,P,Q;A=$packages["github.com/gopherjs/gopherjs/js"];B=$packages["jsexport/battle"];C=$packages["resolv"];R=$sliceType($Uint64);S=$ptrType(B.Vec2D);T=$sliceType(S);U=$ptrType(B.Polygon2D);V=$ptrType(B.PlayerDownsync);W=$sliceType(V);X=$ptrType(B.MeleeBullet);Y=$sliceType(X);Z=$ptrType(B.FireballBullet);AA=$sliceType(Z);AB=$ptrType(A.Object);AC=$sliceType(AB);AD=$funcType([$Float64,$Float64],[AB],false);AE=$funcType([S,T],[AB],false);AF=$funcType([U],[AB],false);AG=$funcType([$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Bool,$Float64],[AB],false);AH=$funcType([$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Int32,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64],[AB],false);AI=$funcType([$Int32,W,Y],[AB],false);AJ=$funcType([$Int,$Int,$Int,$Int],[AB],false);AK=$funcType([$Int32,R,$Uint64],[AB],false);AL=$funcType([$Int32],[AB],false);AM=$funcType([$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$emptyInterface,$String],[AB],false);AN=$funcType([U,$Float64,$Float64,$emptyInterface,$String],[AB],false);AO=$ptrType(C.Space);AP=$funcType([AO],[AC],false);AQ=$ptrType(B.RingBuffer);AR=$ptrType(B.RoomDownsyncFrame);AS=$ptrType(C.Object);AT=$mapType($Int32,AS);AU=$mapType($Int,$Int);AV=$funcType([AQ,AR,AO,AT,$Int32,$Int32,$Int32,$Int32,$Uint32,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,AU],[AB],false);AW=$funcType([$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64,$Float64],[$Float64,$Float64],false);AX=$mapType($String,$emptyInterface);D=function(a,b,c){var{a,b,c,d,e,$s,$r,$c}=$restore(this,{a,b,c});$s=$s||0;s:while(true){switch($s){case 0:d=A.MakeFullWrapper(new B.InputFrameDownsync.ptr(a,b,c));$s=1;case 1:if($c){$c=false;d=d.$blk();}if(d&&d.$blk!==undefined){break s;}e=d;$s=2;case 2:return e;}return;}var $f={$blk:D,$c:true,$r,a,b,c,d,e,$s};return $f;};$pkg.NewInputFrameDownsync=D;E=function(a){var{a,b,c,$s,$r,$c}=$restore(this,{a});$s=$s||0;s:while(true){switch($s){case 0:b=A.MakeFullWrapper(B.NewRingBuffer(a));$s=1;case 1:if($c){$c=false;b=b.$blk();}if(b&&b.$blk!==undefined){break s;}c=b;$s=2;case 2:return c;}return;}var $f={$blk:E,$c:true,$r,a,b,c,$s};return $f;};$pkg.NewRingBufferJs=E;F=function(a,b,c,d){var a,b,c,d;return A.MakeWrapper(C.NewSpace(a,b,c,d));};$pkg.NewCollisionSpaceJs=F;G=function(a,b){var{a,b,c,d,$s,$r,$c}=$restore(this,{a,b});$s=$s||0;s:while(true){switch($s){case 0:c=A.MakeFullWrapper(new B.Vec2D.ptr(a,b));$s=1;case 1:if($c){$c=false;c=c.$blk();}if(c&&c.$blk!==undefined){break s;}d=c;$s=2;case 2:return d;}return;}var $f={$blk:G,$c:true,$r,a,b,c,d,$s};return $f;};$pkg.NewVec2DJs=G;H=function(a,b){var{a,b,c,d,$s,$r,$c}=$restore(this,{a,b});$s=$s||0;s:while(true){switch($s){case 0:c=A.MakeFullWrapper(new B.Polygon2D.ptr(a,b));$s=1;case 1:if($c){$c=false;c=c.$blk();}if(c&&c.$blk!==undefined){break s;}d=c;$s=2;case 2:return d;}return;}var $f={$blk:H,$c:true,$r,a,b,c,d,$s};return $f;};$pkg.NewPolygon2DJs=H;I=function(a){var a;return A.MakeWrapper(new B.Barrier.ptr(a));};$pkg.NewBarrierJs=I;J=function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p;return A.MakeWrapper(new B.PlayerDownsync.ptr(a,b,c,d,e,f,g,i,j,l,p,false,0,0,h,m,n,k,o));};$pkg.NewPlayerDownsyncJs=J;K=function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;return A.MakeWrapper(new B.MeleeBullet.ptr(new B.Bullet.ptr(a,b,c,d,e,f,n,0,g,h,m,i,j,k,l,o,p,q,r)));};$pkg.NewMeleeBulletJs=K;L=function(a,b,c){var{a,b,c,d,e,$s,$r,$c}=$restore(this,{a,b,c});$s=$s||0;s:while(true){switch($s){case 0:d=A.MakeFullWrapper(new B.RoomDownsyncFrame.ptr(a,b,new $Int64(0,0),c,AA.nil,new $Uint64(0,0),false,false));$s=1;case 1:if($c){$c=false;d=d.$blk();}if(d&&d.$blk!==undefined){break s;}e=d;$s=2;case 2:return e;}return;}var $f={$blk:L,$c:true,$r,a,b,c,d,e,$s};return $f;};$pkg.NewRoomDownsyncFrameJs=L;M=function(a){var{a,b,c,d,e,f,g,$s,$r,$c}=$restore(this,{a});$s=$s||0;s:while(true){switch($s){case 0:b=a.Objects();c=$makeSlice(AC,0,b.$length);d=b;e=0;case 1:if(!(e=d.$length)?($throwRuntimeError("index out of range"),undefined):d.$array[d.$offset+e]);g=A.MakeFullWrapper(f);$s=3;case 3:if($c){$c=false;g=g.$blk();}if(g&&g.$blk!==undefined){break s;}c=$append(c,g);e++;$s=1;continue;case 2:$s=-1;return c;}return;}var $f={$blk:M,$c:true,$r,a,b,c,d,e,f,g,$s};return $f;};$pkg.GetCollisionSpaceObjsJs=M;N=function(a,b,c,d,e,f,g,h,i,j,k,l){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,$s,$r,$c}=$restore(this,{a,b,c,d,e,f,g,h,i,j,k,l});$s=$s||0;s:while(true){switch($s){case 0:m=B.GenerateRectCollider(a,b,c,d,e,f,g,h,i,j,k,l);$s=1;case 1:if($c){$c=false;m=m.$blk();}if(m&&m.$blk!==undefined){break s;}n=A.MakeFullWrapper(m);$s=2;case 2:if($c){$c=false;n=n.$blk();}if(n&&n.$blk!==undefined){break s;}o=n;$s=3;case 3:return o;}return;}var $f={$blk:N,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,$s};return $f;};$pkg.GenerateRectColliderJs=N;O=function(a,b,c,d,e){var{a,b,c,d,e,f,g,h,$s,$r,$c}=$restore(this,{a,b,c,d,e});$s=$s||0;s:while(true){switch($s){case 0:f=B.GenerateConvexPolygonCollider(a,b,c,d,e);$s=1;case 1:if($c){$c=false;f=f.$blk();}if(f&&f.$blk!==undefined){break s;}g=A.MakeFullWrapper(f);$s=2;case 2:if($c){$c=false;g=g.$blk();}if(g&&g.$blk!==undefined){break s;}h=g;$s=3;case 3:return h;}return;}var $f={$blk:O,$c:true,$r,a,b,c,d,e,f,g,h,$s};return $f;};$pkg.GenerateConvexPolygonColliderJs=O;P=function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){var{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,$s,$r,$c}=$restore(this,{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p});$s=$s||0;s:while(true){switch($s){case 0:q=B.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p);$s=1;case 1:if($c){$c=false;q=q.$blk();}if(q&&q.$blk!==undefined){break s;}r=A.MakeFullWrapper(q);$s=2;case 2:if($c){$c=false;r=r.$blk();}if(r&&r.$blk!==undefined){break s;}s=r;$s=3;case 3:return s;}return;}var $f={$blk:P,$c:true,$r,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,$s};return $f;};$pkg.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs=P;Q=function(){$global.gopkgs=$externalize($makeMap($String.keyFor,[{k:"NewVec2DJs",v:new AD(G)},{k:"NewPolygon2DJs",v:new AE(H)},{k:"NewBarrierJs",v:new AF(I)},{k:"NewPlayerDownsyncJs",v:new AG(J)},{k:"NewMeleeBulletJs",v:new AH(K)},{k:"NewRoomDownsyncFrameJs",v:new AI(L)},{k:"NewCollisionSpaceJs",v:new AJ(F)},{k:"NewInputFrameDownsync",v:new AK(D)},{k:"NewRingBufferJs",v:new AL(E)},{k:"GenerateRectColliderJs",v:new AM(N)},{k:"GenerateConvexPolygonColliderJs",v:new AN(O)},{k:"GetCollisionSpaceObjsJs",v:new AP(M)},{k:"ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs",v:new AV(P)},{k:"WorldToPolygonColliderBLPos",v:new AW(B.WorldToPolygonColliderBLPos)},{k:"PolygonColliderBLToWorldPos",v:new AW(B.PolygonColliderBLToWorldPos)}]),AX);};$init=function(){$pkg.$init=function(){};var $f,$c=false,$s=0,$r;if(this!==undefined&&this.$blk!==undefined){$f=this;$c=true;$s=$f.$s;$r=$f.$r;}s:while(true){switch($s){case 0:$r=A.$init();$s=1;case 1:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}$r=B.$init();$s=2;case 2:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}$r=C.$init();$s=3;case 3:if($c){$c=false;$r=$r.$blk();}if($r&&$r.$blk!==undefined){break s;}if($pkg===$mainPkg){Q();$mainFinished=true;}}return;}if($f===undefined){$f={$blk:$init};}$f.$s=$s;$f.$r=$r;return $f;};$pkg.$init=$init;return $pkg;})(); +$synthesizeMethods(); +$initAllLinknames(); +var $mainPkg = $packages["jsexport"]; +$packages["runtime"].$init(); +$go($mainPkg.$init, []); +$flushConsole(); + +}).call(this); +//# sourceMappingURL=jsexport.js.map diff --git a/frontend/assets/plugin_scripts/jsexport.js.meta b/frontend/assets/plugin_scripts/jsexport.js.meta new file mode 100644 index 0000000..6fae4d2 --- /dev/null +++ b/frontend/assets/plugin_scripts/jsexport.js.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.0.5", + "uuid": "d41313ca-b2c3-4436-a05f-7e0eb290b1e6", + "isPlugin": true, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/frontend/assets/resources/map/dungeon/map.tmx b/frontend/assets/resources/map/dungeon/map.tmx index a0fda48..b7e54a3 100644 --- a/frontend/assets/resources/map/dungeon/map.tmx +++ b/frontend/assets/resources/map/dungeon/map.tmx @@ -9,10 +9,10 @@ - + - + diff --git a/frontend/assets/resources/pbfiles/room_downsync_frame.proto b/frontend/assets/resources/pbfiles/room_downsync_frame.proto index f80b5c3..f8cc68c 100644 --- a/frontend/assets/resources/pbfiles/room_downsync_frame.proto +++ b/frontend/assets/resources/pbfiles/room_downsync_frame.proto @@ -90,67 +90,71 @@ message MeleeBullet { int32 recoveryFrames = 4; int32 recoveryFramesOnBlock = 5; int32 recoveryFramesOnHit = 6; - sharedprotos.Vec2D moveforward = 7; - double hitboxOffset = 8; - sharedprotos.Vec2D hitboxSize = 9; - int32 originatedRenderFrameId = 10; + double hitboxOffset = 7; + int32 originatedRenderFrameId = 8; // for defender - int32 hitStunFrames = 11; - int32 blockStunFrames = 12; - double pushback = 13; + int32 hitStunFrames = 9; + int32 blockStunFrames = 10; + double pushback = 11; - int32 releaseTriggerType = 14; // 1: rising-edge, 2: falling-edge - int32 damage = 15; + int32 releaseTriggerType = 12; // 1: rising-edge, 2: falling-edge + int32 damage = 13; - int32 offenderJoinIndex = 16; - int32 offenderPlayerId = 17; + int32 offenderJoinIndex = 14; + int32 offenderPlayerId = 15; + + double hitboxSizeX = 16; + double hitboxSizeY = 17; + + double selfMoveforwardX = 18; + double selfMoveforwardY = 19; } message BattleColliderInfo { string stageName = 1; - map strToVec2DListMap = 2; - map strToPolygon2DListMap = 3; - int32 stageDiscreteW = 4; - int32 stageDiscreteH = 5; - int32 stageTileW = 6; - int32 stageTileH = 7; + int32 stageDiscreteW = 2; + int32 stageDiscreteH = 3; + int32 stageTileW = 4; + int32 stageTileH = 5; - int32 intervalToPing = 8; - int32 willKickIfInactiveFor = 9; - int32 boundRoomId = 10; - int32 battleDurationFrames = 12; - int64 battleDurationNanos = 13; - int32 serverFps = 14; - int32 inputDelayFrames = 15; // in the count of render frames - uint32 inputScaleFrames = 16; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) - int32 nstDelayFrames = 17; // 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 = 18; - int32 maxChasingRenderFramesPerUpdate = 19; - int32 playerBattleState = 20; - double rollbackEstimatedDtMillis = 21; - int64 rollbackEstimatedDtNanos = 22; + int32 intervalToPing = 6; + int32 willKickIfInactiveFor = 7; + int32 boundRoomId = 8; + int32 battleDurationFrames = 9; + int64 battleDurationNanos = 10; + int32 serverFps = 11; + int32 inputDelayFrames = 12; // in the count of render frames + uint32 inputScaleFrames = 13; // inputDelayedAndScaledFrameId = ((originalFrameId - InputDelayFrames) >> InputScaleFrames) + 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 = 23; - double virtualGridToWorldRatio = 24; + double worldToVirtualGridRatio = 20; + double virtualGridToWorldRatio = 21; - int32 spAtkLookupFrames = 25; - int32 renderCacheSize = 26; + int32 spAtkLookupFrames = 22; + int32 renderCacheSize = 23; - map meleeSkillConfig = 27; // skillId -> skill + double snapIntoPlatformOverlap = 24; + double snapIntoPlatformThreshold = 25; + int32 jumpingInitVelY = 26; + int32 gravityX = 27; + int32 gravityY = 28; + int32 collisionMinStep = 29; - double snapIntoPlatformOverlap = 28; - double snapIntoPlatformThreshold = 29; - int32 jumpingInitVelY = 30; - int32 gravityX = 31; - int32 gravityY = 32; + bool frameDataLoggingEnabled = 999; } message RoomDownsyncFrame { int32 id = 1; - map players = 2; + repeated PlayerDownsync playersArr = 2; int64 countdownNanos = 3; 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 bool shouldForceResync = 6; + map playerOpPatternToSkillId = 7; } diff --git a/frontend/assets/resources/prefabs/ControlledCharacter.prefab b/frontend/assets/resources/prefabs/ControlledCharacter.prefab index 8fdcd12..07979d3 100644 --- a/frontend/assets/resources/prefabs/ControlledCharacter.prefab +++ b/frontend/assets/resources/prefabs/ControlledCharacter.prefab @@ -97,7 +97,7 @@ "__id__": 1 }, "_children": [], - "_active": false, + "_active": true, "_components": [ { "__id__": 3 @@ -129,7 +129,7 @@ "ctor": "Float64Array", "array": [ 0, - 0, + 45, 0, 0, 0, diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 31ccba3..9d76136 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 209.73151519075364, + 216.6771387800957, 0, 0, 0, diff --git a/frontend/assets/scenes/offline_map_1.fire b/frontend/assets/scenes/offline_map.fire similarity index 99% rename from frontend/assets/scenes/offline_map_1.fire rename to frontend/assets/scenes/offline_map.fire index dc4b778..7059ef0 100644 --- a/frontend/assets/scenes/offline_map_1.fire +++ b/frontend/assets/scenes/offline_map.fire @@ -58,7 +58,7 @@ "_groupIndex": 0, "groupIndex": 0, "autoReleaseAssets": false, - "_id": "368b10b6-88fc-423c-9fcd-545d9fc673bd" + "_id": "8491a86c-bec9-4813-968a-128ca01639e0" }, { "__type__": "cc.Node", @@ -249,7 +249,7 @@ "_id": "3crA1nz5xPSLAnCSLQIPOq" }, { - "__type__": "47d7dy4S4lB2pxqJJlGOoai", + "__type__": "b3810kDSWtD14RhiYzulYVI", "_name": "", "_objFlags": 0, "node": { @@ -278,7 +278,8 @@ "renderFrameIdLagTolerance": 4, "jigglingEps1D": 0.001, "bulletTriggerEnabled": true, - "_id": "4b+kZ46VhC0LCBixXEK2dk" + "closeOnForcedtoResyncNotSelf": true, + "_id": "e5xQdv12xLoIRr0b36Pie+" }, { "__type__": "cc.Node", diff --git a/frontend/assets/scenes/offline_map_1.fire.meta b/frontend/assets/scenes/offline_map.fire.meta similarity index 65% rename from frontend/assets/scenes/offline_map_1.fire.meta rename to frontend/assets/scenes/offline_map.fire.meta index 1ab1461..85fb4b0 100644 --- a/frontend/assets/scenes/offline_map_1.fire.meta +++ b/frontend/assets/scenes/offline_map.fire.meta @@ -1,6 +1,6 @@ { "ver": "1.2.5", - "uuid": "368b10b6-88fc-423c-9fcd-545d9fc673bd", + "uuid": "8491a86c-bec9-4813-968a-128ca01639e0", "asyncLoadAssets": false, "autoReleaseAssets": false, "subMetas": {} diff --git a/frontend/assets/scripts/AttackingCharacter.js b/frontend/assets/scripts/AttackingCharacter.js index 8fa622b..04290eb 100644 --- a/frontend/assets/scripts/AttackingCharacter.js +++ b/frontend/assets/scripts/AttackingCharacter.js @@ -67,7 +67,6 @@ cc.Class({ this.speciesName = null; this.hp = 100; this.maxHp = 100; - this.framesToRecover = 0; this.inAir = true; }, @@ -86,18 +85,18 @@ cc.Class({ }, updateCharacterAnim(rdfPlayer, prevRdfPlayer, forceAnimSwitch) { - // 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 if (this.animComp && this.animComp.node) { - if (0 > rdfPlayer.dirX) { + if (0 > rdfPlayer.DirX) { this.animComp.node.scaleX = (-1.0); - } else if (0 < rdfPlayer.dirX) { + } else if (0 < rdfPlayer.DirX) { this.animComp.node.scaleX = (1.0); } } - let newCharacterState = rdfPlayer.characterState; + let newCharacterState = rdfPlayer.CharacterState; let newAnimName = window.ATK_CHARACTER_STATE_ARR[newCharacterState][1]; let playingAnimName = null; let underlyingAnimationCtrl = null; @@ -110,7 +109,7 @@ cc.Class({ playingAnimName = (!underlyingAnimationCtrl ? null : underlyingAnimationCtrl.name); } - // It turns out that "prevRdfPlayer.characterState" is not useful in this function :) + // It turns out that "prevRdfPlayer.CharacterState" is not useful in this function :) if (newAnimName == playingAnimName && window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(newCharacterState)) { // No need to interrupt // console.warn(`JoinIndex=${rdfPlayer.joinIndex}, not interrupting ${newAnimName} while the playing anim is also ${playingAnimName}, player rdf changed from: ${null == prevRdfPlayer ? null : JSON.stringify(prevRdfPlayer)}, to: ${JSON.stringify(rdfPlayer)}`); @@ -131,7 +130,7 @@ cc.Class({ underlyingAnimationCtrl.gotoAndPlayByFrame(newAnimName, 0, -1); } else { const animationData = underlyingAnimationCtrl._animations[newAnimName]; - let fromAnimFrame = (animationData.frameCount - rdfPlayer.framesToRecover); + let fromAnimFrame = (animationData.frameCount - rdfPlayer.FramesToRecover); if (fromAnimFrame < 0) { // For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames fromAnimFrame = 0; @@ -149,7 +148,7 @@ cc.Class({ } // The "playTimes" counterpart is managed by each "cc.AnimationClip.wrapMode", already preset in the editor. const targetClip = this.animComp.getClips()[newCharacterState]; // The clips follow the exact order in ATK_CHARACTER_STATE - let fromTime = (targetClip.duration - rdfPlayer.framesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here? + let fromTime = (targetClip.duration - rdfPlayer.FramesToRecover / targetClip.sample); // TODO: Anyway to avoid using division here? if (fromTime < 0) { // For Atk1 or Atk2, it's possible that the "meleeBullet.recoveryFrames" is configured to be slightly larger than corresponding animation duration frames fromTime = 0; diff --git a/frontend/assets/scripts/CameraTracker.js b/frontend/assets/scripts/CameraTracker.js index 558b40c..55cfab3 100644 --- a/frontend/assets/scripts/CameraTracker.js +++ b/frontend/assets/scripts/CameraTracker.js @@ -25,7 +25,7 @@ cc.Class({ if (!self.mapScriptIns) return; if (!self.mapScriptIns.selfPlayerInfo) return; if (!self.mapScriptIns.playerRichInfoDict) return; - const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.id); + const selfPlayerRichInfo = self.mapScriptIns.playerRichInfoDict.get(self.mapScriptIns.selfPlayerInfo.Id); if (!selfPlayerRichInfo) return; const selfPlayerNode = selfPlayerRichInfo.node; if (!selfPlayerNode) return; diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 814ce52..e06ac04 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -1,7 +1,6 @@ const i18n = require('LanguageData'); i18n.init(window.language); // languageID should be equal to the one we input in New Language ID input field -const collisions = require('./modules/Collisions'); const RingBuffer = require('./RingBuffer'); window.ALL_MAP_STATES = { @@ -141,28 +140,36 @@ cc.Class({ let previousSelfInput = null, currSelfInput = null; - const joinIndex = self.selfPlayerInfo.joinIndex; - const existingInputFrame = self.recentInputCache.getByFrameId(inputFrameId); - const previousInputFrameDownsyncWithPrediction = self.getCachedInputFrameDownsyncWithPrediction(inputFrameId - 1); - previousSelfInput = (null == previousInputFrameDownsyncWithPrediction ? null : previousInputFrameDownsyncWithPrediction.inputList[joinIndex - 1]); + const joinIndex = self.selfPlayerInfo.JoinIndex; + const existingInputFrame = self.recentInputCache.GetByFrameId(inputFrameId); + const previousInputFrameDownsync = self.recentInputCache.GetByFrameId(inputFrameId - 1); + previousSelfInput = (null == previousInputFrameDownsync ? null : previousInputFrameDownsync.InputList[joinIndex - 1]); if (null != existingInputFrame) { // This could happen upon either [type#1] or [type#2] forceConfirmation, where "refRenderFrame" is accompanied by some "inputFrameDownsyncs". The check here also guarantees that we don't override history console.log(`noDelayInputFrameId=${inputFrameId} already exists in recentInputCache: recentInputCache=${self._stringifyRecentInputCache(false)}`); - return [previousSelfInput, existingInputFrame.inputList[joinIndex - 1]]; + return [previousSelfInput, existingInputFrame.InputList[joinIndex - 1]]; } - const prefabbedInputList = (null == previousInputFrameDownsyncWithPrediction ? new Array(self.playerRichInfoDict.size).fill(0) : previousInputFrameDownsyncWithPrediction.inputList.slice()); + const lastAllConfirmedInputFrame = self.recentInputCache.GetByFrameId(self.lastAllConfirmedInputFrameId); + const prefabbedInputList = new Array(self.playerRichInfoDict.size).fill(0); + // the returned "gopkgs.NewInputFrameDownsync.InputList" is immutable, thus we can only modify the values in "prefabbedInputList" + for (let k in prefabbedInputList) { + if (null != previousInputFrameDownsync) { + prefabbedInputList[k] = previousInputFrameDownsync.InputList[k]; + } + if (0 <= self.lastAllConfirmedInputFrameId && inputFrameId - 1 > self.lastAllConfirmedInputFrameId) { + prefabbedInputList[k] = lastAllConfirmedInputFrame.InputList[k]; + } + // Don't predict "btnA & btnB"! + prefabbedInputList[k] = (prefabbedInputList[k] & 15); + } currSelfInput = self.ctrl.getEncodedInput(); // When "null == existingInputFrame", it'd be safe to say that the realtime "self.ctrl.getEncodedInput()" is for the requested "inputFrameId" prefabbedInputList[(joinIndex - 1)] = currSelfInput; - while (self.recentInputCache.edFrameId <= inputFrameId) { + while (self.recentInputCache.EdFrameId <= inputFrameId) { // Fill the gap - const prefabbedInputFrameDownsync = window.pb.protos.InputFrameDownsync.create({ - inputFrameId: self.recentInputCache.edFrameId, - inputList: prefabbedInputList, - confirmedList: (1 << (self.selfPlayerInfo.joinIndex - 1)) - }); - - self.recentInputCache.put(prefabbedInputFrameDownsync); + const prefabbedInputFrameDownsync = gopkgs.NewInputFrameDownsync(self.recentInputCache.EdFrameId, prefabbedInputList.slice(), (1 << (joinIndex - 1))); + // console.log(`Prefabbed inputFrameId=${prefabbedInputFrameDownsync.InputFrameId}`); + self.recentInputCache.Put(prefabbedInputFrameDownsync); } return [previousSelfInput, currSelfInput]; @@ -185,18 +192,18 @@ cc.Class({ const self = this; let inputFrameUpsyncBatch = []; let batchInputFrameIdSt = self.lastUpsyncInputFrameId + 1; - if (batchInputFrameIdSt < self.recentInputCache.stFrameId) { + if (batchInputFrameIdSt < self.recentInputCache.StFrameId) { // Upon resync, "self.lastUpsyncInputFrameId" might not have been updated properly. - batchInputFrameIdSt = self.recentInputCache.stFrameId; + batchInputFrameIdSt = self.recentInputCache.StFrameId; } for (let i = batchInputFrameIdSt; i <= latestLocalInputFrameId; ++i) { - const inputFrameDownsync = self.recentInputCache.getByFrameId(i); + const inputFrameDownsync = self.recentInputCache.GetByFrameId(i); if (null == inputFrameDownsync) { console.error(`sendInputFrameUpsyncBatch: recentInputCache is NOT having inputFrameId=i: latestLocalInputFrameId=${latestLocalInputFrameId}, recentInputCache=${self._stringifyRecentInputCache(false)}`); } else { const inputFrameUpsync = { inputFrameId: i, - encoded: inputFrameDownsync.inputList[self.selfPlayerInfo.joinIndex - 1], + encoded: inputFrameDownsync.InputList[self.selfPlayerInfo.JoinIndex - 1], }; inputFrameUpsyncBatch.push(inputFrameUpsync); } @@ -205,15 +212,15 @@ cc.Class({ // console.info(`inputFrameUpsyncBatch: ${JSON.stringify(inputFrameUpsyncBatch)}`); const reqData = window.pb.protos.WsReq.encode({ msgId: Date.now(), - playerId: self.selfPlayerInfo.id, + playerId: self.selfPlayerInfo.Id, act: window.UPSYNC_MSG_ACT_PLAYER_CMD, - joinIndex: self.selfPlayerInfo.joinIndex, + joinIndex: self.selfPlayerInfo.JoinIndex, ackingInputFrameId: self.lastAllConfirmedInputFrameId, inputFrameUpsyncBatch: inputFrameUpsyncBatch, }).finish(); window.sendSafely(reqData); self.lastUpsyncInputFrameId = latestLocalInputFrameId; - if (self.lastUpsyncInputFrameId >= self.recentInputCache.edFrameId) { + if (self.lastUpsyncInputFrameId >= self.recentInputCache.EdFrameId) { throw `noDelayInputFrameId=${self.lastUpsyncInputFrameId} == latestLocalInputFrameId=${latestLocalInputFrameId} seems not properly dumped #2: recentInputCache=${self._stringifyRecentInputCache(false)}`; } }, @@ -306,13 +313,17 @@ cc.Class({ self.recentRenderCache = new RingBuffer(self.renderCacheSize); self.selfPlayerInfo = null; // This field is kept for distinguishing "self" and "others". - self.recentInputCache = new RingBuffer((self.renderCacheSize >> 1) + 1); + self.recentInputCache = gopkgs.NewRingBufferJs((self.renderCacheSize >> 1) + 1); - self.collisionSys = new collisions.Collisions(); + const spaceW = self.stageDiscreteW * self.stageTileW; + 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.collisionBarrierIndexPrefix = (1 << 16); // For tracking the movements of barriers, though not yet actually used self.collisionBulletIndexPrefix = (1 << 15); // For tracking the movements of bullets - self.collisionSysMap = new Map(); console.log(`collisionSys & collisionSysMap reset`); @@ -321,6 +332,7 @@ cc.Class({ self.battleState = ALL_BATTLE_STATES.WAITING; self.othersForcedDownsyncRenderFrameDict = new Map(); + self.rdfIdToActuallyUsedInput = new Map(); self.countdownNanos = null; if (self.countdownLabel) { @@ -403,7 +415,6 @@ cc.Class({ console.log(`Received parsedBattleColliderInfo via ws`); // TODO: Upon reconnection, the backend might have already been sending down data that'd trigger "onRoomDownsyncFrame & onInputFrameDownsyncBatch", but frontend could reject those data due to "battleState != PlayerBattleState.ACTIVE". Object.assign(self, parsedBattleColliderInfo); - self.gravityX = parsedBattleColliderInfo.gravityX; // to avoid integer default value 0 accidentally becoming null in "Object.assign(...)" self.tooFastDtIntervalMillis = 0.5 * self.rollbackEstimatedDtMillis; const tiledMapIns = self.node.getComponent(cc.TiledMap); @@ -454,21 +465,19 @@ cc.Class({ } let barrierIdCounter = 0; - const refBoundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node).barriers; - const boundaryObjs = parsedBattleColliderInfo.strToPolygon2DListMap; - for (let k = 0; k < boundaryObjs["Barrier"].eles.length; k++) { - let boundaryObj = boundaryObjs["Barrier"].eles[k]; - const refBoundaryObj = refBoundaryObjs[k]; - // boundaryObj = refBoundaryObj; - const [x0, y0] = [boundaryObj.anchor.x, boundaryObj.anchor.y]; - const newBarrierCollider = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj.points, p => { - return [p.x, p.y]; - })); - newBarrierCollider.data = { - hardPushback: true - }; + const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); + for (let boundaryObj of boundaryObjs.barriers) { + const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(boundaryObj.anchor.x, boundaryObj.anchor.y); + const gopkgsBoundaryPts = Array.from(boundaryObj, p => { + return gopkgs.NewVec2DJs(p.x, p.y); + }); + const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts); + const gopkgsBarrier = gopkgs.NewBarrierJs(gopkgsBoundary); - if (self.showCriticalCoordinateLabels) { + const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier"); + self.gopkgsCollisionSys.Add(newBarrierCollider); + + if (false && self.showCriticalCoordinateLabels) { for (let i = 0; i < boundaryObj.length; ++i) { const barrierVertLabelNode = new cc.Node(); switch (i % 4) { @@ -499,15 +508,14 @@ cc.Class({ } } + // console.log("Created barrier: ", newBarrierCollider); ++barrierIdCounter; const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); - self.collisionSysMap.set(collisionBarrierIndex, newBarrierCollider); - // console.log(`Created new barrier collider: ${collisionBarrierIndex}`); + self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider; } - self.selfPlayerInfo = JSON.parse(cc.sys.localStorage.getItem('selfPlayer')); Object.assign(self.selfPlayerInfo, { - id: self.selfPlayerInfo.playerId + Id: self.selfPlayerInfo.playerId }); const reqData = window.pb.protos.WsReq.encode({ @@ -578,8 +586,22 @@ cc.Class({ this._inputControlEnabled = false; }, - onRoomDownsyncFrame(rdf, accompaniedInputFrameDownsyncBatch) { + onRoomDownsyncFrame(pbRdf /* pb.RoomDownsyncFrame */ , accompaniedInputFrameDownsyncBatch /* pb.InputFrameDownsyncBatch */ ) { + const jsPlayersArr = new Array().fill(null); + for (let k in pbRdf.playersArr) { + 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); + jsPlayersArr[k] = jsPlayer; + } + const jsMeleeBulletsArr = []; + for (let k in pbRdf.meleeBullets) { + 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); + jsMeleeBulletsArr.push(jsBullet); + } + // This function is also applicable to "re-joining". + const rdf = gopkgs.NewRoomDownsyncFrameJs(pbRdf.id, jsPlayersArr, jsMeleeBulletsArr); const self = window.mapIns; self.onInputFrameDownsyncBatch(accompaniedInputFrameDownsyncBatch); // Important to do this step before setting IN_BATTLE if (!self.recentRenderCache) { @@ -588,14 +610,14 @@ cc.Class({ if (ALL_BATTLE_STATES.IN_SETTLEMENT == self.battleState) { return; } - const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id); - let shouldForceDumping2 = (rdf.id >= self.renderFrameId + self.renderFrameIdLagTolerance); - let shouldForceResync = rdf.shouldForceResync; - const notSelfUnconfirmed = (0 == (rdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.joinIndex - 1)))); + const shouldForceDumping1 = (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id); + let shouldForceDumping2 = (rdf.Id >= self.renderFrameId + self.renderFrameIdLagTolerance); + let shouldForceResync = pbRdf.shouldForceResync; + const notSelfUnconfirmed = (0 == (pbRdf.backendUnconfirmedMask & (1 << (self.selfPlayerInfo.JoinIndex - 1)))); if (notSelfUnconfirmed) { shouldForceDumping2 = false; shouldForceResync = false; - self.othersForcedDownsyncRenderFrameDict.set(rdf.id, rdf); + self.othersForcedDownsyncRenderFrameDict.set(rdf.Id, rdf); } /* TODO @@ -603,7 +625,7 @@ cc.Class({ If "BackendUnconfirmedMask" is non-all-1 and contains the current player, show a label/button to hint manual reconnection. Note that the continuity of "recentInputCache" is not a good indicator, because due to network delay upon a [type#1 forceConfirmation] a player might just lag in upsync networking and have all consecutive inputFrameIds locally. */ - const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null]; + const [dumpRenderCacheRet, oldStRenderFrameId, oldEdRenderFrameId] = (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) ? self.recentRenderCache.setByFrameId(rdf, rdf.Id) : [window.RING_BUFF_CONSECUTIVE_SET, null, null]; if (window.RING_BUFF_FAILED_TO_SET == dumpRenderCacheRet) { throw `Failed to dump render cache#1 (maybe recentRenderCache too small)! rdf.id=${rdf.id}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`; } @@ -616,51 +638,44 @@ cc.Class({ } // The logic below applies to (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id || window.RING_BUFF_NON_CONSECUTIVE_SET == dumpRenderCacheRet) - const players = rdf.players; - self._initPlayerRichInfoDict(players); + self.playerOpPatternToSkillId = pbRdf.playerOpPatternToSkillId; + self._initPlayerRichInfoDict(rdf.PlayersArr); // Show the top status indicators for IN_BATTLE if (self.playersInfoNode) { const playersInfoScriptIns = self.playersInfoNode.getComponent("PlayersInfo"); - for (let i in players) { - playersInfoScriptIns.updateData(players[i]); + for (let i in pbRdf.playersArr) { + playersInfoScriptIns.updateData(pbRdf.playersArr[i]); } } if (shouldForceDumping1 || shouldForceDumping2 || shouldForceResync) { // In fact, not having "window.RING_BUFF_CONSECUTIVE_SET == dumpRenderCacheRet" should already imply that "self.renderFrameId <= rdf.id", but here we double check and log the anomaly - if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.id) { - console.log('On battle started! renderFrameId=', rdf.id); + if (window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START == rdf.Id) { + console.log('On battle started! renderFrameId=', rdf.Id); } else { - self.hideFindingPlayersGUI(rdf); - console.warn(`Got resync@localRenderFrameId=${self.renderFrameId} -> rdf.id=${rdf.id} & rdf.backendUnconfirmedMask=${rdf.backendUnconfirmedMask}, @lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}, @localRecentInputCache=${mapIns._stringifyRecentInputCache(false)}`); + self.hideFindingPlayersGUI(); + console.warn('On battle resynced! renderFrameId=', rdf.Id); } - - self.renderFrameId = rdf.id; + self.renderFrameId = rdf.Id; self.lastRenderFrameIdTriggeredAt = performance.now(); // In this case it must be true that "rdf.id > chaserRenderFrameId". - self.chaserRenderFrameId = rdf.id; - const candidateLastAllConfirmedInputFrame = self._convertToInputFrameId(rdf.id - 1, self.inputDelayFrames); - if (self.lastAllConfirmedInputFrame < candidateLastAllConfirmedInputFrame) { - self.lastAllConfirmedInputFrame = candidateLastAllConfirmedInputFrame; - } + self.chaserRenderFrameId = rdf.Id; const canvasNode = self.canvasNode; self.ctrl = canvasNode.getComponent("TouchEventsManager"); self.enableInputControls(); self.transitToState(ALL_MAP_STATES.VISUAL); self.battleState = ALL_BATTLE_STATES.IN_BATTLE; + } - if (self.countdownToBeginGameNode && self.countdownToBeginGameNode.parent) { - self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode); - } + if (self.countdownToBeginGameNode && self.countdownToBeginGameNode.parent) { + self.countdownToBeginGameNode.parent.removeChild(self.countdownToBeginGameNode); + } - if (null != self.musicEffectManagerScriptIns) { - self.musicEffectManagerScriptIns.playBGM(); - } - } else { - console.warn(`Anomaly when onRoomDownsyncFrame is called by rdf=${JSON.stringify(rdf)}, recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`); + if (null != self.musicEffectManagerScriptIns) { + self.musicEffectManagerScriptIns.playBGM(); } // [WARNING] Leave all graphical updates in "update(dt)" by "applyRoomDownsyncFrameDynamics" @@ -714,8 +729,11 @@ cc.Class({ return true; }, - onInputFrameDownsyncBatch(batch) { + onInputFrameDownsyncBatch(batch /* []*pb.InputFrameDownsync */ ) { // TODO: find some kind of synchronization mechanism against "getOrPrefabInputFrameUpsync"! + if (null == batch) { + return; + } const self = this; if (!self.recentInputCache) { return; @@ -733,27 +751,26 @@ cc.Class({ } // [WARNING] Take all "inputFrameDownsync" from backend as all-confirmed, it'll be later checked by "rollbackAndChase". self.lastAllConfirmedInputFrameId = inputFrameDownsyncId; - const localInputFrame = self.recentInputCache.getByFrameId(inputFrameDownsyncId); + const localInputFrame = self.recentInputCache.GetByFrameId(inputFrameDownsyncId); if (null != localInputFrame && null == firstPredictedYetIncorrectInputFrameId && - !self.equalInputLists(localInputFrame.inputList, inputFrameDownsync.inputList) + !self.equalInputLists(localInputFrame.InputList, inputFrameDownsync.inputList) ) { firstPredictedYetIncorrectInputFrameId = inputFrameDownsyncId; } inputFrameDownsync.confirmedList = (1 << self.playerRichInfoDict.size) - 1; - const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.setByFrameId(inputFrameDownsync, inputFrameDownsync.inputFrameId); + const inputFrameDownsyncLocal = gopkgs.NewInputFrameDownsync(inputFrameDownsync.inputFrameId, inputFrameDownsync.inputList, inputFrameDownsync.confirmedList); // "battle.InputFrameDownsync" in "jsexport" + //console.log(`Confirmed inputFrameId=${inputFrameDownsync.inputFrameId}`); + const [ret, oldStFrameId, oldEdFrameId] = self.recentInputCache.SetByFrameId(inputFrameDownsyncLocal, inputFrameDownsync.inputFrameId); if (window.RING_BUFF_FAILED_TO_SET == ret) { throw `Failed to dump input cache (maybe recentInputCache too small)! inputFrameDownsync.inputFrameId=${inputFrameDownsync.inputFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}; recentRenderCache=${self._stringifyRecentRenderCache(false)}, recentInputCache=${self._stringifyRecentInputCache(false)}`; } } if (null == firstPredictedYetIncorrectInputFrameId) return; - const inputFrameId1 = firstPredictedYetIncorrectInputFrameId; - const renderFrameId1 = self._convertToFirstUsedRenderFrameId(inputFrameId1, self.inputDelayFrames); // a.k.a. "firstRenderFrameIdUsingIncorrectInputFrameId" - if (renderFrameId1 >= self.renderFrameId) return; // No need to rollback when "renderFrameId1 == self.renderFrameId", because the "corresponding delayedInputFrame for renderFrameId1" is NOT YET EXECUTED BY NOW, it just went through "++self.renderFrameId" in "update(dt)" and javascript-runtime is mostly single-threaded in our programmable range. - + const renderFrameId1 = self._convertToFirstUsedRenderFrameId(firstPredictedYetIncorrectInputFrameId, self.inputDelayFrames) - 1; if (renderFrameId1 >= self.chaserRenderFrameId) return; /* @@ -768,19 +785,21 @@ cc.Class({ -------------------------------------------------------- */ // The actual rollback-and-chase would later be executed in update(dt). - console.warn(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${inputFrameId1} -lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); + console.warn(`Mismatched input detected, resetting chaserRenderFrameId: ${self.chaserRenderFrameId}->${renderFrameId1} by firstPredictedYetIncorrectInputFrameId: ${firstPredictedYetIncorrectInputFrameId} +lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId} +recentInputCache=${self._stringifyRecentInputCache(false)} +batchInputFrameIdRange=[${batch[0].inputFrameId}, ${batch[batch.length - 1].inputFrameId}]`); self.chaserRenderFrameId = renderFrameId1; }, - onPlayerAdded(rdf) { + onPlayerAdded(rdf /* pb.RoomDownsyncFrame */ ) { const self = this; // Update the "finding player" GUI and show it if not previously present if (!self.findingPlayerNode.parent) { self.showPopupInCanvas(self.findingPlayerNode); } let findingPlayerScriptIns = self.findingPlayerNode.getComponent("FindingPlayer"); - findingPlayerScriptIns.updatePlayersInfo(rdf.players); + findingPlayerScriptIns.updatePlayersInfo(rdf.playersArr); }, onBattleStopped() { @@ -788,7 +807,7 @@ lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); if (ALL_BATTLE_STATES.IN_BATTLE != self.battleState) { return; } - self._stringifyRecentInputAndRenderCacheCorrespondingly(); + self._stringifyRdfIdToActuallyUsedInput(); window.closeWSConnection(constants.RET_CODE.BATTLE_STOPPED); self.battleState = ALL_BATTLE_STATES.IN_SETTLEMENT; self.countdownNanos = null; @@ -819,24 +838,28 @@ lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); const [wx, wy] = self.virtualGridToWorldPos(vx, vy); newPlayerNode.setPosition(wx, wy); playerScriptIns.mapNode = self.node; - const halfColliderWidth = playerDownsyncInfo.colliderRadius, - halfColliderHeight = playerDownsyncInfo.colliderRadius + playerDownsyncInfo.colliderRadius; // avoid multiplying + const colliderRadius = playerDownsyncInfo.ColliderRadius; + const halfColliderWidth = colliderRadius, + halfColliderHeight = colliderRadius + colliderRadius; // avoid multiplying const colliderWidth = halfColliderWidth + halfColliderWidth, colliderHeight = halfColliderHeight + halfColliderHeight; // avoid multiplying - const leftPadding = self.snapIntoPlatformOverlap, - rightPadding = self.snapIntoPlatformOverlap, - topPadding = self.snapIntoPlatformOverlap, - bottomPadding = self.snapIntoPlatformOverlap; - const cpos = self.virtualGridToPolygonColliderBLPos(vx, vy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding); // the collider center is kept having integer coords - const pts = [[0, 0], [leftPadding + colliderWidth + rightPadding, 0], [leftPadding + colliderWidth + rightPadding, bottomPadding + colliderHeight + topPadding], [0, bottomPadding + colliderHeight + topPadding]]; - // [WARNING] The animNode "anchor & offset" are tuned to fit in this collider by "ControlledCharacter prefab & AttackingCharacter.js"! - const newPlayerCollider = self.collisionSys.createPolygon(cpos[0], cpos[1], pts); + const [cx, cy] = gopkgs.WorldToPolygonColliderBLPos(wx, wy, halfColliderWidth, halfColliderHeight, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.snapIntoPlatformOverlap, self.spaceOffsetX, self.spaceOffsetY); + 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; - newPlayerCollider.data = playerDownsyncInfo; - self.collisionSysMap.set(collisionPlayerIndex, newPlayerCollider); + self.gopkgsCollisionSysMap[collisionPlayerIndex] = newPlayerCollider; - console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.colliderRadius}`); + console.log(`Created new player collider: joinIndex=${joinIndex}, colliderRadius=${playerDownsyncInfo.ColliderRadius}`); safelyAddChild(self.node, newPlayerNode); setLocalZOrder(newPlayerNode, 5); @@ -880,12 +903,12 @@ lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); } if (prevChaserRenderFrameId < nextChaserRenderFrameId) { // Do not execute "rollbackAndChase" when "prevChaserRenderFrameId == nextChaserRenderFrameId", otherwise if "nextChaserRenderFrameId == self.renderFrameId" we'd be wasting computing power once. - self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.collisionSys, self.collisionSysMap, true); + self.rollbackAndChase(prevChaserRenderFrameId, nextChaserRenderFrameId, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, true); } let t2 = performance.now(); // Inside the following "self.rollbackAndChase" actually ROLLS FORWARD w.r.t. the corresponding delayedInputFrame, REGARDLESS OF whether or not "self.chaserRenderFrameId == self.renderFrameId" now. - const latestRdfResults = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); + const latestRdfResults = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, false); let prevRdf = latestRdfResults[0], rdf = latestRdfResults[1]; /* @@ -901,8 +924,7 @@ lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}`); if (self.lastAllConfirmedInputFrameId >= delayedInputFrameId && !self.equalRoomDownsyncFrames(othersForcedDownsyncRenderFrame, rdf)) { console.warn(`Mismatched render frame@rdf.id=${rdf.id} w/ inputFrameId=${delayedInputFrameId}: rdf=${JSON.stringify(rdf)} -othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)} -${self._stringifyRecentInputAndRenderCacheCorrespondingly()}`); +othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame)}`); // closeWSConnection(constants.RET_CODE.CLIENT_MISMATCHED_RENDER_FRAME, ""); // self.onManualRejoinRequired("[DEBUG] CLIENT_MISMATCHED_RENDER_FRAME"); rdf = othersForcedDownsyncRenderFrame; @@ -1007,10 +1029,9 @@ ${self._stringifyRecentInputAndRenderCacheCorrespondingly()}`); self.findingPlayerNode.parent.removeChild(self.findingPlayerNode); }, - onBattleReadyToStart(rdf) { + onBattleReadyToStart(rdf /* pb.RoomDownsyncFrame */ ) { const self = this; - const players = rdf.players; - self._initPlayerRichInfoDict(players); + const players = rdf.playersArr; // Show the top status indicators for IN_BATTLE if (self.playersInfoNode) { @@ -1039,13 +1060,14 @@ ${self._stringifyRecentInputAndRenderCacheCorrespondingly()}`); applyRoomDownsyncFrameDynamics(rdf, prevRdf) { const self = this; - for (let [playerId, playerRichInfo] of self.playerRichInfoDict.entries()) { - const currPlayerDownsync = rdf.players[playerId]; - const prevRdfPlayer = (null == prevRdf ? null : prevRdf.players[playerId]); - const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.virtualGridX, currPlayerDownsync.virtualGridY); - //const justJiggling = (self.jigglingEps1D >= Math.abs(wx - playerRichInfo.node.x) && self.jigglingEps1D >= Math.abs(wy - playerRichInfo.node.y)); + const playersArr = rdf.PlayersArr; + for (let k in playersArr) { + const currPlayerDownsync = playersArr[k]; + const prevRdfPlayer = (null == prevRdf ? null : prevRdf.PlayersArr[k]); + const [wx, wy] = self.virtualGridToWorldPos(currPlayerDownsync.VirtualGridX, currPlayerDownsync.VirtualGridY); + const playerRichInfo = self.playerRichInfoArr[k]; playerRichInfo.node.setPosition(wx, wy); - playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.speed); + playerRichInfo.scriptIns.updateSpeed(currPlayerDownsync.Speed); playerRichInfo.scriptIns.updateCharacterAnim(currPlayerDownsync, prevRdfPlayer, false); } @@ -1056,6 +1078,167 @@ ${self._stringifyRecentInputAndRenderCacheCorrespondingly()}`); } }, + rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) { + const self = this; + let prevLatestRdf = null, + latestRdf = null; + for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) { + const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"! + if (null == currRdf) { + throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`; + } + const j = self._convertToInputFrameId(i, self.inputDelayFrames); + const delayedInputFrame = self.recentInputCache.GetByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing + if (null == delayedInputFrame) { + // 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)}`; + } + + const jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames); + if (self.frameDataLoggingEnabled) { + const actuallyUsedInputClone = delayedInputFrame.InputList.slice(); + const inputFrameDownsyncClone = { + inputFrameId: delayedInputFrame.InputFrameId, + inputList: actuallyUsedInputClone, + confirmedList: delayedInputFrame.ConfirmedList, + }; + 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); + + if (true == isChasing) { + // [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic! + self.chaserRenderFrameId = nextRdf.Id; + } else if (nextRdf.Id == self.chaserRenderFrameId + 1) { + self.chaserRenderFrameId = nextRdf.Id; // To avoid redundant calculation + } + self.recentRenderCache.setByFrameId(nextRdf, nextRdf.Id); + prevLatestRdf = currRdf; + latestRdf = nextRdf; + } + + return [prevLatestRdf, latestRdf]; + }, + + _initPlayerRichInfoDict(playersArr) { + const self = this; + for (let k in playersArr) { + const immediatePlayerInfo = playersArr[k]; + const playerId = immediatePlayerInfo.Id; + if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys + self.playerRichInfoDict.set(playerId, immediatePlayerInfo); + const joinIndex = immediatePlayerInfo.joinIndex || immediatePlayerInfo.JoinIndex; + const vx = immediatePlayerInfo.virtualGridX || immediatePlayerInfo.VirtualGridX; + const vy = immediatePlayerInfo.virtualGridY || immediatePlayerInfo.VirtualGridY; + const nodeAndScriptIns = self.spawnPlayerNode(joinIndex, vx, vy, immediatePlayerInfo); + + Object.assign(self.playerRichInfoDict.get(playerId), { + node: nodeAndScriptIns[0], + scriptIns: nodeAndScriptIns[1], + }); + + const selfPlayerId = self.selfPlayerInfo.Id; + if (selfPlayerId == playerId) { + self.selfPlayerInfo.JoinIndex = immediatePlayerInfo.JoinIndex; + nodeAndScriptIns[1].showArrowTipNode(); + } + } + self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); + self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { + self.playerRichInfoArr[playerRichInfo.JoinIndex - 1] = playerRichInfo; + }); + }, + + _stringifyRecentInputCache(usefullOutput) { + const self = this; + if (true == usefullOutput) { + let s = []; + for (let i = self.recentInputCache.StFrameId; i < self.recentInputCache.EdFrameId; ++i) { + s.push(JSON.stringify(self.recentInputCache.GetByFrameId(i))); + } + + return s.join('\n'); + } + return `[stInputFrameId=${self.recentInputCache.StFrameId}, edInputFrameId=${self.recentInputCache.EdFrameId})`; + }, + + _stringifyGopkgRoomDownsyncFrame(rdf) { + let s = []; + s.push(`{`); + s.push(` id: ${rdf.Id}`); + s.push(` players: [`); + for (let k in rdf.PlayersArr) { + const player = rdf.PlayersArr[k]; + s.push(` {joinIndex: ${player.JoinIndex}, id: ${player.Id}, vx: ${player.VirtualGridX}, vy: ${player.VirtualGridY}, velX: ${player.VelX}, velY: ${player.VelY}}`); + } + s.push(` ]`); + s.push(`}`); + return s.join("\n"); + }, + + _stringifyRecentRenderCache(usefullOutput) { + const self = this; + if (true == usefullOutput) { + let s = []; + for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) { + const rdf = self.recentRenderCache.getByFrameId(i); + s.push(self._stringifyGopkgRoomDownsyncFrame(rdf)); + } + + return s.join("\n"); + } + return `[stRenderFrameId=${self.recentRenderCache.stFrameId}, edRenderFrameId=${self.recentRenderCache.edFrameId})`; + }, + + playerDownsyncStr(playerDownsync) { + if (null == playerDownsync) return ""; + return `{${playerDownsync.JoinIndex},${playerDownsync.VirtualGridX},${playerDownsync.VirtualGridY},${playerDownsync.VelX},${playerDownsync.VelY},${playerDownsync.FramesToRecover},${playerDownsync.InAir ? 1 : 0}}`; + }, + + inputFrameDownsyncStr(inputFrameDownsync) { + if (null == inputFrameDownsync) return ""; + const self = this; + let s = []; + s.push(`InputFrameId:${inputFrameDownsync.inputFrameId}`); + let ss = []; + for (let k in inputFrameDownsync.inputList) { + ss.push(`"${inputFrameDownsync.inputList[k]}"`); + } + s.push(`InputList:[${ss.join(',')}]`); + // The "confirmedList" is not worth comparing, because frontend might actually use a non-all-confirmed inputFrame during its history, as long as it's correctly predicted. + //s.push(`ConfirmedList:${inputFrameDownsync.confirmedList}`); + + return s.join(','); + }, + + _stringifyRdfIdToActuallyUsedInput() { + const self = this; + let s = []; + for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; i++) { + const actuallyUsedInputClone = self.rdfIdToActuallyUsedInput.get(i); + const rdf = self.recentRenderCache.getByFrameId(i); + const playersStrBldr = []; + for (let k in rdf.PlayersArr) { + playersStrBldr.push(self.playerDownsyncStr(rdf.PlayersArr[k])); + } + s.push(`rdfId:${i} +players:[${playersStrBldr.join(',')}] +actuallyUsedinputList:{${self.inputFrameDownsyncStr(actuallyUsedInputClone)}}`); + } + + return s.join('\n'); + }, + + stringifyColliderCenterInWorld(playerCollider, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { + 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) { const self = this; const leftPadding = self.snapIntoPlatformOverlap, @@ -1066,603 +1249,34 @@ ${self._stringifyRecentInputAndRenderCacheCorrespondingly()}`); let g = self.g; g.clear(); - for (let k in self.collisionSys._bvh._bodies) { - const body = self.collisionSys._bvh._bodies[k]; - if (!body._polygon) continue; - if (null != body.data && null != body.data.joinIndex) { + const collisionSpaceObjs = gopkgs.GetCollisionSpaceObjsJs(self.gopkgsCollisionSys); + 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) { + if (1 == body.Data.JoinIndex) { g.strokeColor = cc.Color.BLUE; } else { g.strokeColor = cc.Color.RED; } + padding = self.snapIntoPlatformOverlap; } else { // barrier g.strokeColor = cc.Color.WHITE; } - g.moveTo(body.x, body.y); - const cnt = body._coords.length; - for (let j = 0; j < cnt; j += 2) { - const x = body._coords[j], - y = body._coords[j + 1]; + const points = body.Shape.Points; + const wpos = [body.X - self.spaceOffsetX, body.Y - self.spaceOffsetY]; + g.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]; g.lineTo(x, y); } - g.lineTo(body.x, body.y); + g.lineTo(wpos[0], wpos[1]); g.stroke(); } - // For convenience of recovery upon reconnection, active bullets are always created & immediately removed from "collisionSys" within "applyInputFrameDownsyncDynamicsOnSingleRenderFrame" - - for (let k in rdf.meleeBullets) { - const meleeBullet = rdf.meleeBullets[k]; - if ( - meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= rdf.id - && - meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > rdf.id - ) { - const offender = rdf.players[meleeBullet.offenderPlayerId]; - if (1 == offender.joinIndex) { - g.strokeColor = cc.Color.BLUE; - } else { - g.strokeColor = cc.Color.RED; - } - - let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis" - if (0 > offender.dirX) { - xfac = -1; - } - const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); - const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset; - const bulletWy = offenderWy; - const halfColliderWidth = meleeBullet.hitboxSize.x * 0.5, - halfColliderHeight = meleeBullet.hitboxSize.y * 0.5; // avoid multiplying - const bulletCpos = self.worldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding); - const pts = [[0, 0], [leftPadding + meleeBullet.hitboxSize.x + rightPadding, 0], [leftPadding + meleeBullet.hitboxSize.x + rightPadding, bottomPadding + meleeBullet.hitboxSize.y + topPadding], [0, bottomPadding + meleeBullet.hitboxSize.y + topPadding]]; - - g.moveTo(bulletCpos[0], bulletCpos[1]); - for (let j = 0; j < pts.length; j += 1) { - g.lineTo(pts[j][0] + bulletCpos[0], pts[j][1] + bulletCpos[1]); - } - g.lineTo(bulletCpos[0], bulletCpos[1]); - g.stroke(); - } - } } }, - - getCachedInputFrameDownsyncWithPrediction(inputFrameId) { - const self = this; - const inputFrameDownsync = self.recentInputCache.getByFrameId(inputFrameId); - if (null != inputFrameDownsync && inputFrameId > self.lastAllConfirmedInputFrameId) { - const lastAllConfirmedInputFrame = self.recentInputCache.getByFrameId(self.lastAllConfirmedInputFrameId); - if (null != lastAllConfirmedInputFrame) { - for (let i = 0; i < inputFrameDownsync.inputList.length; ++i) { - if (i == (self.selfPlayerInfo.joinIndex - 1)) continue; - inputFrameDownsync.inputList[i] = (lastAllConfirmedInputFrame.inputList[i] & 15); // Don't predict attack input! - } - } - } - - return inputFrameDownsync; - }, - - // TODO: Write unit-test for this function to compare with its backend counter part - applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRenderFrame, collisionSys, collisionSysMap) { - const self = this; - const leftPadding = self.snapIntoPlatformOverlap, - rightPadding = self.snapIntoPlatformOverlap, - topPadding = self.snapIntoPlatformOverlap, - bottomPadding = self.snapIntoPlatformOverlap; - const nextRenderFramePlayers = {}; - for (let playerId in currRenderFrame.players) { - const currPlayerDownsync = currRenderFrame.players[playerId]; - nextRenderFramePlayers[playerId] = { - id: playerId, - virtualGridX: currPlayerDownsync.virtualGridX, - virtualGridY: currPlayerDownsync.virtualGridY, - dirX: currPlayerDownsync.dirX, - dirY: currPlayerDownsync.dirY, - velX: currPlayerDownsync.velX, - velY: currPlayerDownsync.velY, - characterState: currPlayerDownsync.characterState, - inAir: true, - speed: currPlayerDownsync.speed, - battleState: currPlayerDownsync.battleState, - score: currPlayerDownsync.score, - removed: currPlayerDownsync.removed, - joinIndex: currPlayerDownsync.joinIndex, - framesToRecover: (0 < currPlayerDownsync.framesToRecover ? currPlayerDownsync.framesToRecover - 1 : 0), - hp: currPlayerDownsync.hp, - maxHp: currPlayerDownsync.maxHp, - }; - } - - const nextRenderFrameMeleeBullets = []; - const effPushbacks = new Array(self.playerRichInfoArr.length); - const hardPushbackNorms = new Array(self.playerRichInfoArr.length); - - // 1. Process player inputs - /* - [WARNING] Player input alone WOULD NOT take "characterState" into any "ATK_CHARACTER_STATE_IN_AIR_SET", only after the calculation of "effPushbacks" do we know exactly whether or not a player is "inAir", the finalize the transition of "thatPlayerInNextFrame.characterState". - */ - if (null != delayedInputFrame) { - const delayedInputFrameForPrevRenderFrame = self.getCachedInputFrameDownsyncWithPrediction(self._convertToInputFrameId(currRenderFrame.id - 1, self.inputDelayFrames)); - const inputList = delayedInputFrame.inputList; - for (let j in self.playerRichInfoArr) { - const joinIndex = parseInt(j) + 1; - const playerRichInfo = self.playerRichInfoArr[j]; - const playerId = playerRichInfo.id; - const currPlayerDownsync = currRenderFrame.players[playerId]; - const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; - if (0 < thatPlayerInNextFrame.framesToRecover) { - // No need to process inputs for this player, but there might be bullet pushbacks on this player - continue; - } - - const decodedInput = self.ctrl.decodeInput(inputList[joinIndex - 1]); - const prevDecodedInput = (null == delayedInputFrameForPrevRenderFrame ? null : self.ctrl.decodeInput(delayedInputFrameForPrevRenderFrame.inputList[joinIndex - 1])); - const prevBtnALevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnALevel); - const prevBtnBLevel = (null == prevDecodedInput ? 0 : prevDecodedInput.btnBLevel); - if (1 == decodedInput.btnBLevel && 0 == prevBtnBLevel) { - const characStateAlreadyInAir = window.ATK_CHARACTER_STATE_IN_AIR_SET.has(thatPlayerInNextFrame.characterState); - const characStateIsInterruptWaivable = window.ATK_CHARACTER_STATE_INTERRUPT_WAIVE_SET.has(thatPlayerInNextFrame.characterState); - if ( - !characStateAlreadyInAir - && - characStateIsInterruptWaivable - ) { - thatPlayerInNextFrame.velY = self.jumpingInitVelY; - if (1 == joinIndex) { - console.log(`playerId=${playerId}, joinIndex=${joinIndex} jumped at {renderFrame.id: ${currRenderFrame.id}, virtualX: ${currPlayerDownsync.virtualGridX}, virtualY: ${currPlayerDownsync.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); - } - } - } - - if (1 == decodedInput.btnALevel && 0 == prevBtnALevel) { - const punchSkillId = 1; - const punch = window.pb.protos.MeleeBullet.create(self.meleeSkillConfig[punchSkillId]); - thatPlayerInNextFrame.framesToRecover = punch.recoveryFrames; - punch.battleLocalId = self.bulletBattleLocalIdCounter++; - punch.offenderJoinIndex = joinIndex; - punch.offenderPlayerId = playerId; - punch.originatedRenderFrameId = currRenderFrame.id; - nextRenderFrameMeleeBullets.push(punch); - // console.log(`playerId=${playerId}, joinIndex=${joinIndex} triggered a rising-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); - - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atk1[0]; - if (false == currPlayerDownsync.inAir) { - thatPlayerInNextFrame.velX = 0; // prohibits simultaneous movement with Atk1 on the ground - } - } else if (0 == decodedInput.btnALevel && 1 == prevBtnALevel) { - // console.log(`playerId=${playerId} triggered a falling-edge of btnA at renderFrame.id=${currRenderFrame.id}, delayedInputFrame.id=${delayedInputFrame.inputFrameId}`); - } else { - // No bullet trigger, process joystick movement inputs. - if (0 != decodedInput.dx || 0 != decodedInput.dy) { - // Update directions and thus would eventually update moving animation accordingly - thatPlayerInNextFrame.dirX = decodedInput.dx; - thatPlayerInNextFrame.dirY = decodedInput.dy; - thatPlayerInNextFrame.velX = decodedInput.dx * currPlayerDownsync.speed; - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Walking[0]; - } else { - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; - thatPlayerInNextFrame.velX = 0; - } - } - } - } - - // 2. Process player movement - for (let j in self.playerRichInfoArr) { - const joinIndex = parseInt(j) + 1; - effPushbacks[joinIndex - 1] = [0.0, 0.0]; - const playerRichInfo = self.playerRichInfoArr[j]; - const playerId = playerRichInfo.id; - const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; - const playerCollider = collisionSysMap.get(collisionPlayerIndex); - const currPlayerDownsync = currRenderFrame.players[playerId]; - const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; - // Reset playerCollider position from the "virtual grid position" - const newVpos = [currPlayerDownsync.virtualGridX + currPlayerDownsync.velX, currPlayerDownsync.virtualGridY + currPlayerDownsync.velY]; - if (thatPlayerInNextFrame.velY == self.jumpingInitVelY) { - // This step can be waived, but putting the jumping inclination here makes it easier to read logs. - newVpos[1] += self.jumpingInitVelY; - } - const halfColliderWidth = self.playerRichInfoArr[j].colliderRadius, - halfColliderHeight = self.playerRichInfoArr[j].colliderRadius + self.playerRichInfoArr[j].colliderRadius; // avoid multiplying - const newCpos = self.virtualGridToPolygonColliderBLPos(newVpos[0], newVpos[1], halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding); - playerCollider.x = newCpos[0]; - playerCollider.y = newCpos[1]; - - if (currPlayerDownsync.inAir) { - thatPlayerInNextFrame.velX += self.gravityX; - thatPlayerInNextFrame.velY += self.gravityY; - } - } - - // 3. Add bullet colliders into collision system - const bulletColliders = new Map(); // Will all be removed at the end of `applyInputFrameDownsyncDynamicsOnSingleRenderFrame` due to the need for being rollback-compatible - const removedBulletsAtCurrFrame = new Set(); - for (let k in currRenderFrame.meleeBullets) { - const meleeBullet = currRenderFrame.meleeBullets[k]; - if ( - meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames <= currRenderFrame.id - && - meleeBullet.originatedRenderFrameId + meleeBullet.startupFrames + meleeBullet.activeFrames > currRenderFrame.id - ) { - const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId; - const collisionOffenderIndex = self.collisionPlayerIndexPrefix + meleeBullet.offenderJoinIndex; - const offenderCollider = collisionSysMap.get(collisionOffenderIndex); - const offender = currRenderFrame.players[meleeBullet.offenderPlayerId]; - - let xfac = 1; // By now, straight Punch offset doesn't respect "y-axis" - if (0 > offender.dirX) { - xfac = -1; - } - const [offenderWx, offenderWy] = self.virtualGridToWorldPos(offender.virtualGridX, offender.virtualGridY); - const bulletWx = offenderWx + xfac * meleeBullet.hitboxOffset; - const bulletWy = offenderWy; - const halfColliderWidth = meleeBullet.hitboxSize.x * 0.5, - halfColliderHeight = meleeBullet.hitboxSize.y * 0.5; - const bulletCpos = self.worldToPolygonColliderBLPos(bulletWx, bulletWy, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding); - const pts = [[0, 0], [leftPadding + meleeBullet.hitboxSize.x + rightPadding, 0], [leftPadding + meleeBullet.hitboxSize.x + rightPadding, bottomPadding + meleeBullet.hitboxSize.y + topPadding], [0, bottomPadding + meleeBullet.hitboxSize.y + topPadding]]; - const newBulletCollider = collisionSys.createPolygon(bulletCpos[0], bulletCpos[1], pts); - newBulletCollider.data = meleeBullet; - collisionSysMap.set(collisionBulletIndex, newBulletCollider); - bulletColliders.set(collisionBulletIndex, newBulletCollider); - } - } - - // 4. Invoke collision system stepping - collisionSys.update(); - const result = collisionSys.createResult(); // Can I reuse a "self.collisionSysResult" object throughout the whole battle? - - // 5. Calc pushbacks for each player (after its movement) w/o bullets - for (let j in self.playerRichInfoArr) { - const joinIndex = parseInt(j) + 1; - const playerRichInfo = self.playerRichInfoArr[j]; - const playerId = playerRichInfo.id; - const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; - const playerCollider = collisionSysMap.get(collisionPlayerIndex); - const potentials = playerCollider.potentials(); - hardPushbackNorms[joinIndex - 1] = self.calcHardPushbacksNorms(playerCollider, potentials, result, self.snapIntoPlatformOverlap, effPushbacks[joinIndex - 1]); - - const currPlayerDownsync = currRenderFrame.players[playerId]; - const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; - const halfColliderWidth = self.playerRichInfoArr[j].colliderRadius, - halfColliderHeight = self.playerRichInfoArr[j].colliderRadius + self.playerRichInfoArr[j].colliderRadius; // avoid multiplying - - let fallStopping = false; - let possiblyFallStoppedOnAnotherPlayer = false; - for (const potential of potentials) { - let [isBarrier, isAnotherPlayer, isBullet] = [true == potential.data.hardPushback, null != potential.data.joinIndex, null != potential.data.offenderJoinIndex]; - // ignore bullets for this step - if (isBullet) continue; - // Test if the player collides with the wall/another player - if (!playerCollider.collides(potential, result)) continue; - - const normAlignmentWithGravity = (result.overlap_x * 0 + result.overlap_y * (-1.0)); - const landedOnGravityPushback = (self.snapIntoPlatformThreshold < normAlignmentWithGravity); // prevents false snapping on the lateral sides - let pushback = [result.overlap * result.overlap_x, result.overlap * result.overlap_y]; - if (landedOnGravityPushback) { - // kindly note that one player might land on top of another player - pushback = [(result.overlap - self.snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap) * result.overlap_y]; - thatPlayerInNextFrame.inAir = false; - } - 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. - */ - pushback = [(result.overlap - self.snapIntoPlatformOverlap * 2) * result.overlap_x, (result.overlap - self.snapIntoPlatformOverlap * 2) * result.overlap_y]; // will overwrite the previous pushback value if "landedOnGravityPushback" is also true - } - for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) { - // remove pushback component on the directions of "hardPushbackNorms[joinIndex-1]" (by now those hardPushbacks are already accounted in "effPushbacks[joinIndex-1]") - const projectedMagnitude = pushback[0] * hardPushbackNorm[0] + pushback[1] * hardPushbackNorm[1]; - if (isBarrier - || - (isAnotherPlayer && 0 > projectedMagnitude) - ) { - // [WARNING] Pushing by another player is different from pushing by barrier! - // Otherwise the player couldn't be pushed by another player to opposite dir of a side wall - pushback[0] -= projectedMagnitude * hardPushbackNorm[0]; - pushback[1] -= projectedMagnitude * hardPushbackNorm[1]; - } - } - - effPushbacks[joinIndex - 1][0] += pushback[0]; - effPushbacks[joinIndex - 1][1] += pushback[1]; - // It's not meaningful to log the virtual positions and velocities inside this step. - if (currPlayerDownsync.inAir && landedOnGravityPushback) { - fallStopping = true; - if (isAnotherPlayer) { - possiblyFallStoppedOnAnotherPlayer = true; - } - } - - if (1 == joinIndex) { - if (fallStopping) { - /* - console.info(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} fallStopping#1: -{renderFrame.id: ${currRenderFrame.id}, possiblyFallStoppedOnAnotherPlayer: ${possiblyFallStoppedOnAnotherPlayer}} -playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}`); - */ - } else if (currPlayerDownsync.inAir && isBarrier && !landedOnGravityPushback) { - /* - console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir & pushed back by barrier & not landed: -{renderFrame.id: ${currRenderFrame.id}} -playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`); - */ - } else if (currPlayerDownsync.inAir && isAnotherPlayer) { - console.warn(`playerId=${playerId}, joinIndex=${currPlayerDownsync.joinIndex} inAir and pushed back by another player -{renderFrame.id: ${currRenderFrame.id}} -playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, anotherPlayerColliderPos=${self.stringifyColliderCenterInWorld(potential, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}, landedOnGravityPushback=${landedOnGravityPushback}, fallStopping=${fallStopping}, overlayMag=${result.overlap.toFixed(4)}, len(hardPushbackNorms)=${hardPushbackNorms.length}`); - } - } - } - - if (fallStopping) { - thatPlayerInNextFrame.velX = 0; - thatPlayerInNextFrame.velY = 0; - thatPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Idle1[0]; - thatPlayerInNextFrame.framesToRecover = 0; - } - if (currPlayerDownsync.inAir) { - thatPlayerInNextFrame.characterState = window.toInAirConjugate(thatPlayerInNextFrame.characterState); - } - } - - // 6. Check bullet-anything collisions - bulletColliders.forEach((bulletCollider, collisionBulletIndex) => { - const potentials = bulletCollider.potentials(); - const offender = currRenderFrame.players[bulletCollider.data.offenderPlayerId]; - let shouldRemove = false; - for (const potential of potentials) { - if (null != potential.data && potential.data.joinIndex == bulletCollider.data.offenderJoinIndex) continue; - if (!bulletCollider.collides(potential, result)) continue; - if (null != potential.data && null != potential.data.joinIndex) { - const playerId = potential.data.id; - const joinIndex = potential.data.joinIndex; - let xfac = 1; - if (0 > offender.dirX) { - xfac = -1; - } - // Only for straight punch, there's no y-pushback - let bulletPushback = [-xfac * bulletCollider.data.pushback, 0]; - // console.log(`playerId=${playerId}, joinIndex=${joinIndex} is supposed to be pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`); - for (let hardPushbackNorm of hardPushbackNorms[joinIndex - 1]) { - const projectedMagnitude = bulletPushback[0] * hardPushbackNorm[0] + bulletPushback[1] * hardPushbackNorm[1]; - if (0 > projectedMagnitude) { - // Otherwise when smashing into a wall the atked player would be pushed into the wall first and only got back in the next renderFrame, not what I want here - bulletPushback[0] -= (projectedMagnitude * hardPushbackNorm[0]); - bulletPushback[1] -= (projectedMagnitude * hardPushbackNorm[1]); - // console.log(`playerId=${playerId}, joinIndex=${joinIndex} reducing bulletPushback=${JSON.stringify(bulletPushback)} by ${JSON.stringify([projectedMagnitude * hardPushbackNorm[0], projectedMagnitude * hardPushbackNorm[1]])} where hardPushbackNorm=${JSON.stringify(hardPushbackNorm)}, projectedMagnitude=${projectedMagnitude} at renderFrame.id=${currRenderFrame.id}`); - } - } - // console.log(`playerId=${playerId}, joinIndex=${joinIndex} is actually pushed back by meleeBullet for bulletPushback=${JSON.stringify(bulletPushback)} at renderFrame.id=${currRenderFrame.id}`); - effPushbacks[joinIndex - 1][0] += bulletPushback[0]; - effPushbacks[joinIndex - 1][1] += bulletPushback[1]; - const [atkedPlayerInCurFrame, atkedPlayerInNextFrame] = [currRenderFrame.players[potential.data.id], nextRenderFramePlayers[potential.data.id]]; - atkedPlayerInNextFrame.characterState = window.ATK_CHARACTER_STATE.Atked1[0]; - if (atkedPlayerInCurFrame.inAir) { - atkedPlayerInNextFrame.characterState = window.toInAirConjugate(atkedPlayerInNextFrame.characterState); - } - const oldFramesToRecover = atkedPlayerInNextFrame.framesToRecover; - atkedPlayerInNextFrame.framesToRecover = (oldFramesToRecover > bulletCollider.data.hitStunFrames ? oldFramesToRecover : bulletCollider.data.hitStunFrames); // In case the hit player is already stun, we extend it - } - shouldRemove = true; - } - if (shouldRemove) { - removedBulletsAtCurrFrame.add(collisionBulletIndex); - } - }); - - // [WARNING] Remove bullets from collisionSys ANYWAY for the convenience of rollback - for (let k in currRenderFrame.meleeBullets) { - const meleeBullet = currRenderFrame.meleeBullets[k]; - const collisionBulletIndex = self.collisionBulletIndexPrefix + meleeBullet.battleLocalId; - if (collisionSysMap.has(collisionBulletIndex)) { - const bulletCollider = collisionSysMap.get(collisionBulletIndex); - bulletCollider.remove(); - collisionSysMap.delete(collisionBulletIndex); - } - if (removedBulletsAtCurrFrame.has(collisionBulletIndex)) continue; - nextRenderFrameMeleeBullets.push(meleeBullet); - } - - // 7. Get players out of stuck barriers if there's any - for (let j in self.playerRichInfoArr) { - const joinIndex = parseInt(j) + 1; - const playerId = self.playerRichInfoArr[j].id; - const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; - const playerCollider = collisionSysMap.get(collisionPlayerIndex); - // Update "virtual grid position" - const currPlayerDownsync = currRenderFrame.players[playerId]; - const thatPlayerInNextFrame = nextRenderFramePlayers[playerId]; - const halfColliderWidth = self.playerRichInfoArr[j].colliderRadius, - halfColliderHeight = self.playerRichInfoArr[j].colliderRadius + self.playerRichInfoArr[j].colliderRadius; // avoid multiplying - const newVpos = self.polygonColliderBLToVirtualGridPos(playerCollider.x - effPushbacks[joinIndex - 1][0], playerCollider.y - effPushbacks[joinIndex - 1][1], halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding); - thatPlayerInNextFrame.virtualGridX = newVpos[0]; - thatPlayerInNextFrame.virtualGridY = newVpos[1]; - - if (1 == thatPlayerInNextFrame.joinIndex) { - if (currPlayerDownsync.inAir && !thatPlayerInNextFrame.inAir) { - console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} fallStopping#2: -{nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}} - calculated from <- playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); - } else if (!currPlayerDownsync.inAir && thatPlayerInNextFrame.inAir) { - console.warn(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} took off: -{nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}} - calculated from <- playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); - } else if (thatPlayerInNextFrame.inAir && 0 != thatPlayerInNextFrame.velY) { - /* - console.log(`playerId=${playerId}, joinIndex=${thatPlayerInNextFrame.joinIndex} inAir trajectory: -{nextRenderFrame.id: ${currRenderFrame.id + 1}, nextVirtualX: ${thatPlayerInNextFrame.virtualGridX}, nextVirtualY: ${thatPlayerInNextFrame.virtualGridY}, nextVelX: ${thatPlayerInNextFrame.velX}, nextVelY: ${thatPlayerInNextFrame.velY}}; - calculated from <- playerColliderPos=${self.stringifyColliderCenterInWorld(playerCollider, halfColliderWidth, halfColliderHeight, topPadding, bottomPadding, leftPadding, rightPadding)}, effPushback={${effPushbacks[joinIndex - 1][0].toFixed(3)}, ${effPushbacks[joinIndex - 1][1].toFixed(3)}}`); - */ - } - } - } - - return window.pb.protos.RoomDownsyncFrame.create({ - id: currRenderFrame.id + 1, - players: nextRenderFramePlayers, - meleeBullets: nextRenderFrameMeleeBullets, - }); - }, - - rollbackAndChase(renderFrameIdSt, renderFrameIdEd, collisionSys, collisionSysMap, isChasing) { - /* - This function eventually calculates a "RoomDownsyncFrame" where "RoomDownsyncFrame.id == renderFrameIdEd" if not interruptted. - */ - const self = this; - let prevLatestRdf = null, - latestRdf = null; - for (let i = renderFrameIdSt; i < renderFrameIdEd; i++) { - const currRdf = self.recentRenderCache.getByFrameId(i); // typed "RoomDownsyncFrame"; [WARNING] When "true == isChasing" and using Firefox, this function could be interruptted by "onRoomDownsyncFrame(rdf)" asynchronously anytime, making this line return "null"! - if (null == currRdf) { - throw `Couldn't find renderFrame for i=${i} to rollback (are you using Firefox?), self.renderFrameId=${self.renderFrameId}, lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, might've been interruptted by onRoomDownsyncFrame`; - } - const j = self._convertToInputFrameId(i, self.inputDelayFrames); - const delayedInputFrame = self.recentInputCache.getByFrameId(j); // Don't make prediction here, the inputFrameDownsyncs in recentInputCache was already predicted while prefabbing - if (null == delayedInputFrame) { - // 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)}`; - } - const nextRdf = self.applyInputFrameDownsyncDynamicsOnSingleRenderFrame(delayedInputFrame, currRdf, collisionSys, collisionSysMap); - - if (true == isChasing) { - // [WARNING] Move the cursor "self.chaserRenderFrameId" when "true == isChasing", keep in mind that "self.chaserRenderFrameId" is not monotonic! - self.chaserRenderFrameId = nextRdf.id; - } else if (nextRdf.id == self.chaserRenderFrameId + 1) { - self.chaserRenderFrameId = nextRdf.id; // To avoid redundant calculation - } - self.recentRenderCache.setByFrameId(nextRdf, nextRdf.id); - prevLatestRdf = currRdf; - latestRdf = nextRdf; - } - - return [prevLatestRdf, latestRdf]; - }, - - _initPlayerRichInfoDict(players) { - const self = this; - for (let k in players) { - const playerId = parseInt(k); - if (self.playerRichInfoDict.has(playerId)) continue; // Skip already put keys - const immediatePlayerInfo = players[playerId]; - self.playerRichInfoDict.set(playerId, immediatePlayerInfo); - - const nodeAndScriptIns = self.spawnPlayerNode(immediatePlayerInfo.joinIndex, immediatePlayerInfo.virtualGridX, immediatePlayerInfo.virtualGridY, immediatePlayerInfo); - - Object.assign(self.playerRichInfoDict.get(playerId), { - node: nodeAndScriptIns[0], - scriptIns: nodeAndScriptIns[1], - }); - - if (self.selfPlayerInfo.id == playerId) { - self.selfPlayerInfo = Object.assign(self.selfPlayerInfo, immediatePlayerInfo); - nodeAndScriptIns[1].showArrowTipNode(); - } - } - self.playerRichInfoArr = new Array(self.playerRichInfoDict.size); - self.playerRichInfoDict.forEach((playerRichInfo, playerId) => { - self.playerRichInfoArr[playerRichInfo.joinIndex - 1] = playerRichInfo; - }); - }, - - _stringifyRecentInputCache(usefullOutput) { - const self = this; - if (true == usefullOutput) { - let s = []; - for (let i = self.recentInputCache.stFrameId; i < self.recentInputCache.edFrameId; ++i) { - s.push(JSON.stringify(self.recentInputCache.getByFrameId(i))); - } - - return s.join('\n'); - } - return `[stInputFrameId=${self.recentInputCache.stFrameId}, edInputFrameId=${self.recentInputCache.edFrameId})`; - }, - - _stringifyRecentRenderCache(usefullOutput) { - const self = this; - if (true == usefullOutput) { - let s = []; - for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) { - s.push(JSON.stringify(self.recentRenderCache.getByFrameId(i))); - } - - return s.join('\n'); - } - return `[stRenderFrameId=${self.recentRenderCache.stFrameId}, edRenderFrameId=${self.recentRenderCache.edFrameId})`; - }, - - _stringifyRecentInputAndRenderCacheCorrespondingly() { - const self = this; - let s = []; - s.push(`@lastAllConfirmedInputFrameId=${self.lastAllConfirmedInputFrameId}, @renderFrameId=${self.renderFrameId}, @chaserRenderFrameId=${self.chaserRenderFrameId}`); - - for (let i = self.recentRenderCache.stFrameId; i < self.recentRenderCache.edFrameId; ++i) { - let jPrev = self._convertToInputFrameId(i - 1, self.inputDelayFrames); - let j = self._convertToInputFrameId(i, self.inputDelayFrames); - if (i == self.recentRenderCache.stFrameId || j > jPrev) { - s.push(JSON.stringify(self.recentInputCache.getByFrameId(j))); - } - s.push(JSON.stringify(self.recentRenderCache.getByFrameId(i))); - } - return s.join('\n'); - }, - - worldToVirtualGridPos(x, y) { - // [WARNING] Introduces loss of precision! - const self = this; - // 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. - let virtualGridX = Math.round(x * self.worldToVirtualGridRatio); - let virtualGridY = Math.round(y * self.worldToVirtualGridRatio); - return [virtualGridX, virtualGridY]; - }, - - virtualGridToWorldPos(vx, vy) { - // No loss of precision - const self = this; - return [vx * self.virtualGridToWorldRatio, vy * self.virtualGridToWorldRatio]; - }, - - worldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { - return [wx - halfBoundingW - leftPadding, wy - halfBoundingH - bottomPadding]; - }, - - polygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { - return [cx + halfBoundingW + leftPadding, cy + halfBoundingH + bottomPadding]; - }, - - polygonColliderBLToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { - const self = this; - const [wx, wy] = self.polygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding); - return self.worldToVirtualGridPos(wx, wy) - }, - - virtualGridToPolygonColliderBLPos(vx, vy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { - const self = this; - const [wx, wy] = self.virtualGridToWorldPos(vx, vy); - return self.worldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) - }, - - stringifyColliderCenterInWorld(playerCollider, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding) { - return `{${(playerCollider.x + leftPadding + halfBoundingW).toFixed(2)}, ${(playerCollider.y + bottomPadding + halfBoundingH).toFixed(2)}}`; - }, - - calcHardPushbacksNorms(collider, potentials, result, snapIntoPlatformOverlap, effPushback) { - const self = this; - let ret = []; - for (const potential of potentials) { - if (null == potential.data || !(true == potential.data.hardPushback)) continue; - if (!collider.collides(potential, result)) continue; - // ALWAY snap into hardPushbacks! - // [overlay_x, overlap_y] is the unit vector that points into the platform - const pushback = [(result.overlap - snapIntoPlatformOverlap) * result.overlap_x, (result.overlap - snapIntoPlatformOverlap) * result.overlap_y]; - ret.push([result.overlap_x, result.overlap_y]); - effPushback[0] += pushback[0]; - effPushback[1] += pushback[1]; - } - - return ret; - }, }); diff --git a/frontend/assets/scripts/OfflineMap.js b/frontend/assets/scripts/OfflineMap.js index 72272a9..1959b32 100644 --- a/frontend/assets/scripts/OfflineMap.js +++ b/frontend/assets/scripts/OfflineMap.js @@ -13,7 +13,7 @@ cc.Class({ onLoad() { const self = this; window.mapIns = self; - self.showCriticalCoordinateLabels = true; + self.showCriticalCoordinateLabels = false; const mapNode = self.node; const canvasNode = mapNode.parent; @@ -31,6 +31,7 @@ cc.Class({ self.inputDelayFrames = 8; self.inputScaleFrames = 2; self.inputFrameUpsyncDelayTolerance = 2; + self.collisionMinStep = 8; self.renderCacheSize = 1024; self.serverFps = 60; @@ -41,32 +42,11 @@ cc.Class({ self.worldToVirtualGridRatio = 1000; self.virtualGridToWorldRatio = 1.0 / self.worldToVirtualGridRatio; - self.meleeSkillConfig = { - 1: { - // for offender - startupFrames: 10, - activeFrames: 20, - recoveryFrames: 34, // usually but not always "startupFrames+activeFrames", I hereby set it to be 1 frame more than the actual animation to avoid critical transition, i.e. when the animation is 1 frame from ending but "rdfPlayer.framesToRecover" is already counted 0 and the player triggers an other same attack, making an effective bullet trigger but no animation is played due to same animName is still playing - recoveryFramesOnBlock: 34, - recoveryFramesOnHit: 34, - moveforward: { - x: 0, - y: 0, - }, - hitboxOffset: 12.0, // should be about the radius of the PlayerCollider - hitboxSize: { - x: 23.0, - y: 32.0, - }, - - // for defender - hitStunFrames: 18, - blockStunFrames: 9, - pushback: 8.0, - releaseTriggerType: 1, // 1: rising-edge, 2: falling-edge - damage: 5 - } - }; + 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 -> ..." @@ -76,7 +56,7 @@ cc.Class({ 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) + [self.gravityX, self.gravityY] = [0, -0.5 * self.worldToVirtualGridRatio]; // unit: (virtual grid length/renderFrame^2) const tiledMapIns = self.node.getComponent(cc.TiledMap); @@ -89,7 +69,6 @@ cc.Class({ tiledMapIns.tmxAsset = null; mapNode.removeAllChildren(); - self._resetCurrentMatch(); if (self.showCriticalCoordinateLabels) { const drawer = new cc.Node(); @@ -101,25 +80,30 @@ cc.Class({ self.g = g; } - tiledMapIns.tmxAsset = tmxAsset; const newMapSize = tiledMapIns.getMapSize(); const newTileSize = tiledMapIns.getTileSize(); self.node.setContentSize(newMapSize.width * newTileSize.width, newMapSize.height * newTileSize.height); self.node.setPosition(cc.v2(0, 0)); + self.stageDiscreteW = newMapSize.width; + self.stageDiscreteH = newMapSize.height; + self.stageTileW = newTileSize.width; + self.stageTileH = newTileSize.height; + + self._resetCurrentMatch(); let barrierIdCounter = 0; const boundaryObjs = tileCollisionManager.extractBoundaryObjects(self.node); for (let boundaryObj of boundaryObjs.barriers) { - const x0 = boundaryObj.anchor.x, - y0 = boundaryObj.anchor.y; + const gopkgsBoundaryAnchor = gopkgs.NewVec2DJs(boundaryObj.anchor.x, boundaryObj.anchor.y); + const gopkgsBoundaryPts = Array.from(boundaryObj, p => { + return gopkgs.NewVec2DJs(p.x, p.y); + }); + const gopkgsBoundary = gopkgs.NewPolygon2DJs(gopkgsBoundaryAnchor, gopkgsBoundaryPts); + const gopkgsBarrier = gopkgs.NewBarrierJs(gopkgsBoundary); - const newBarrier = self.collisionSys.createPolygon(x0, y0, Array.from(boundaryObj, p => { - return [p.x, p.y]; - })); - newBarrier.data = { - hardPushback: true - }; + const newBarrierCollider = gopkgs.GenerateConvexPolygonColliderJs(gopkgsBoundary, self.spaceOffsetX, self.spaceOffsetY, gopkgsBarrier, "Barrier"); + self.gopkgsCollisionSys.Add(newBarrierCollider); if (false && self.showCriticalCoordinateLabels) { for (let i = 0; i < boundaryObj.length; ++i) { @@ -152,20 +136,20 @@ cc.Class({ } } - // console.log("Created barrier: ", newBarrier); + // console.log("Created barrier: ", newBarrierCollider); ++barrierIdCounter; const collisionBarrierIndex = (self.collisionBarrierIndexPrefix + barrierIdCounter); - self.collisionSysMap.set(collisionBarrierIndex, newBarrier); + self.gopkgsCollisionSysMap[collisionBarrierIndex] = newBarrierCollider; } const startRdf = window.pb.protos.RoomDownsyncFrame.create({ id: window.MAGIC_ROOM_DOWNSYNC_FRAME_ID.BATTLE_START, - players: { - 10: window.pb.protos.PlayerDownsync.create({ + playersArr: [ + window.pb.protos.PlayerDownsync.create({ id: 10, joinIndex: 1, - virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[0], - virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[0].x, boundaryObjs.playerStartingPositions[0].y)[1], + virtualGridX: boundaryObjs.playerStartingPositions[0].x * self.worldToVirtualGridRatio, + virtualGridY: boundaryObjs.playerStartingPositions[0].y * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio, colliderRadius: 12, characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0], @@ -176,11 +160,11 @@ cc.Class({ velY: 0, inAir: true, }), - 11: window.pb.protos.PlayerDownsync.create({ + window.pb.protos.PlayerDownsync.create({ id: 11, joinIndex: 2, - virtualGridX: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[0], - virtualGridY: self.worldToVirtualGridPos(boundaryObjs.playerStartingPositions[1].x, boundaryObjs.playerStartingPositions[1].y)[1], + virtualGridX: boundaryObjs.playerStartingPositions[1].x * self.worldToVirtualGridRatio, + virtualGridY: boundaryObjs.playerStartingPositions[1].y * self.worldToVirtualGridRatio, speed: 1 * self.worldToVirtualGridRatio, colliderRadius: 12, characterState: window.ATK_CHARACTER_STATE.InAirIdle1[0], @@ -191,12 +175,13 @@ cc.Class({ velY: 0, inAir: true, }), - } + ] }); + self.selfPlayerInfo = { - id: 11 + Id: 10, + JoinIndex: 1, }; - self._initPlayerRichInfoDict(startRdf.players); self.onRoomDownsyncFrame(startRdf); self.battleState = ALL_BATTLE_STATES.IN_BATTLE; @@ -219,12 +204,12 @@ cc.Class({ currSelfInput = null; const noDelayInputFrameId = self._convertToInputFrameId(self.renderFrameId, 0); // It's important that "inputDelayFrames == 0" here if (self.shouldGenerateInputFrameUpsync(self.renderFrameId)) { - const prevAndCurrInputs = self._generateInputFrameUpsync(noDelayInputFrameId); + const prevAndCurrInputs = self.getOrPrefabInputFrameUpsync(noDelayInputFrameId); prevSelfInput = prevAndCurrInputs[0]; currSelfInput = prevAndCurrInputs[1]; } - const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.collisionSys, self.collisionSysMap, false); + const [prevRdf, rdf] = self.rollbackAndChase(self.renderFrameId, self.renderFrameId + 1, self.gopkgsCollisionSys, self.gopkgsCollisionSysMap, false); self.applyRoomDownsyncFrameDynamics(rdf, prevRdf); self.showDebugBoundaries(rdf); ++self.renderFrameId; @@ -235,4 +220,5 @@ cc.Class({ } } }, + }); diff --git a/frontend/assets/scripts/OfflineMap.js.meta b/frontend/assets/scripts/OfflineMap.js.meta index 4da3509..bb0072c 100644 --- a/frontend/assets/scripts/OfflineMap.js.meta +++ b/frontend/assets/scripts/OfflineMap.js.meta @@ -1,6 +1,6 @@ { "ver": "1.0.5", - "uuid": "47d7dcb8-4b89-41da-9c6a-2499463a86a2", + "uuid": "b3810903-496b-43d7-8461-898cee958548", "isPlugin": false, "loadPluginInWeb": true, "loadPluginInNative": true, diff --git a/frontend/assets/scripts/WsSessionMgr.js b/frontend/assets/scripts/WsSessionMgr.js index 4174857..7559608 100644 --- a/frontend/assets/scripts/WsSessionMgr.js +++ b/frontend/assets/scripts/WsSessionMgr.js @@ -197,6 +197,9 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) { break; case constants.RET_CODE.BATTLE_STOPPED: // deliberately do nothing + if (mapIns.frameDataLoggingEnabled) { + console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}`); + } break; case constants.RET_CODE.PLAYER_NOT_ADDABLE_TO_ROOM: case constants.RET_CODE.PLAYER_NOT_READDABLE_TO_ROOM: @@ -211,7 +214,9 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) { case constants.RET_CODE.PLAYER_NOT_FOUND: case constants.RET_CODE.PLAYER_CHEATING: case 1006: // Peer(i.e. the backend) gone unexpectedly - console.warn(`${mapIns._stringifyRecentInputAndRenderCacheCorrespondingly()}`); + if (mapIns.frameDataLoggingEnabled) { + console.warn(`${mapIns._stringifyRdfIdToActuallyUsedInput()}`); + } window.clearLocalStorageAndBackToLoginScene(true); break; default: diff --git a/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js b/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js index fbae99a..80d2474 100644 --- a/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js +++ b/frontend/assets/scripts/modules/room_downsync_frame_proto_bundle.forcemsg.js @@ -3966,9 +3966,7 @@ $root.protos = (function() { * @property {number|null} [recoveryFrames] MeleeBullet recoveryFrames * @property {number|null} [recoveryFramesOnBlock] MeleeBullet recoveryFramesOnBlock * @property {number|null} [recoveryFramesOnHit] MeleeBullet recoveryFramesOnHit - * @property {sharedprotos.Vec2D|null} [moveforward] MeleeBullet moveforward * @property {number|null} [hitboxOffset] MeleeBullet hitboxOffset - * @property {sharedprotos.Vec2D|null} [hitboxSize] MeleeBullet hitboxSize * @property {number|null} [originatedRenderFrameId] MeleeBullet originatedRenderFrameId * @property {number|null} [hitStunFrames] MeleeBullet hitStunFrames * @property {number|null} [blockStunFrames] MeleeBullet blockStunFrames @@ -3977,6 +3975,10 @@ $root.protos = (function() { * @property {number|null} [damage] MeleeBullet damage * @property {number|null} [offenderJoinIndex] MeleeBullet offenderJoinIndex * @property {number|null} [offenderPlayerId] MeleeBullet offenderPlayerId + * @property {number|null} [hitboxSizeX] MeleeBullet hitboxSizeX + * @property {number|null} [hitboxSizeY] MeleeBullet hitboxSizeY + * @property {number|null} [selfMoveforwardX] MeleeBullet selfMoveforwardX + * @property {number|null} [selfMoveforwardY] MeleeBullet selfMoveforwardY */ /** @@ -4042,14 +4044,6 @@ $root.protos = (function() { */ MeleeBullet.prototype.recoveryFramesOnHit = 0; - /** - * MeleeBullet moveforward. - * @member {sharedprotos.Vec2D|null|undefined} moveforward - * @memberof protos.MeleeBullet - * @instance - */ - MeleeBullet.prototype.moveforward = null; - /** * MeleeBullet hitboxOffset. * @member {number} hitboxOffset @@ -4058,14 +4052,6 @@ $root.protos = (function() { */ MeleeBullet.prototype.hitboxOffset = 0; - /** - * MeleeBullet hitboxSize. - * @member {sharedprotos.Vec2D|null|undefined} hitboxSize - * @memberof protos.MeleeBullet - * @instance - */ - MeleeBullet.prototype.hitboxSize = null; - /** * MeleeBullet originatedRenderFrameId. * @member {number} originatedRenderFrameId @@ -4130,6 +4116,38 @@ $root.protos = (function() { */ MeleeBullet.prototype.offenderPlayerId = 0; + /** + * MeleeBullet hitboxSizeX. + * @member {number} hitboxSizeX + * @memberof protos.MeleeBullet + * @instance + */ + MeleeBullet.prototype.hitboxSizeX = 0; + + /** + * MeleeBullet hitboxSizeY. + * @member {number} hitboxSizeY + * @memberof protos.MeleeBullet + * @instance + */ + MeleeBullet.prototype.hitboxSizeY = 0; + + /** + * MeleeBullet selfMoveforwardX. + * @member {number} selfMoveforwardX + * @memberof protos.MeleeBullet + * @instance + */ + MeleeBullet.prototype.selfMoveforwardX = 0; + + /** + * MeleeBullet selfMoveforwardY. + * @member {number} selfMoveforwardY + * @memberof protos.MeleeBullet + * @instance + */ + MeleeBullet.prototype.selfMoveforwardY = 0; + /** * Creates a new MeleeBullet instance using the specified properties. * @function create @@ -4166,28 +4184,32 @@ $root.protos = (function() { writer.uint32(/* id 5, wireType 0 =*/40).int32(message.recoveryFramesOnBlock); if (message.recoveryFramesOnHit != null && Object.hasOwnProperty.call(message, "recoveryFramesOnHit")) writer.uint32(/* id 6, wireType 0 =*/48).int32(message.recoveryFramesOnHit); - if (message.moveforward != null && Object.hasOwnProperty.call(message, "moveforward")) - $root.sharedprotos.Vec2D.encode(message.moveforward, writer.uint32(/* id 7, wireType 2 =*/58).fork()).ldelim(); if (message.hitboxOffset != null && Object.hasOwnProperty.call(message, "hitboxOffset")) - writer.uint32(/* id 8, wireType 1 =*/65).double(message.hitboxOffset); - if (message.hitboxSize != null && Object.hasOwnProperty.call(message, "hitboxSize")) - $root.sharedprotos.Vec2D.encode(message.hitboxSize, writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); + writer.uint32(/* id 7, wireType 1 =*/57).double(message.hitboxOffset); if (message.originatedRenderFrameId != null && Object.hasOwnProperty.call(message, "originatedRenderFrameId")) - writer.uint32(/* id 10, wireType 0 =*/80).int32(message.originatedRenderFrameId); + writer.uint32(/* id 8, wireType 0 =*/64).int32(message.originatedRenderFrameId); if (message.hitStunFrames != null && Object.hasOwnProperty.call(message, "hitStunFrames")) - writer.uint32(/* id 11, wireType 0 =*/88).int32(message.hitStunFrames); + writer.uint32(/* id 9, wireType 0 =*/72).int32(message.hitStunFrames); if (message.blockStunFrames != null && Object.hasOwnProperty.call(message, "blockStunFrames")) - writer.uint32(/* id 12, wireType 0 =*/96).int32(message.blockStunFrames); + writer.uint32(/* id 10, wireType 0 =*/80).int32(message.blockStunFrames); if (message.pushback != null && Object.hasOwnProperty.call(message, "pushback")) - writer.uint32(/* id 13, wireType 1 =*/105).double(message.pushback); + writer.uint32(/* id 11, wireType 1 =*/89).double(message.pushback); if (message.releaseTriggerType != null && Object.hasOwnProperty.call(message, "releaseTriggerType")) - writer.uint32(/* id 14, wireType 0 =*/112).int32(message.releaseTriggerType); + writer.uint32(/* id 12, wireType 0 =*/96).int32(message.releaseTriggerType); if (message.damage != null && Object.hasOwnProperty.call(message, "damage")) - writer.uint32(/* id 15, wireType 0 =*/120).int32(message.damage); + writer.uint32(/* id 13, wireType 0 =*/104).int32(message.damage); if (message.offenderJoinIndex != null && Object.hasOwnProperty.call(message, "offenderJoinIndex")) - writer.uint32(/* id 16, wireType 0 =*/128).int32(message.offenderJoinIndex); + writer.uint32(/* id 14, wireType 0 =*/112).int32(message.offenderJoinIndex); if (message.offenderPlayerId != null && Object.hasOwnProperty.call(message, "offenderPlayerId")) - writer.uint32(/* id 17, wireType 0 =*/136).int32(message.offenderPlayerId); + writer.uint32(/* id 15, wireType 0 =*/120).int32(message.offenderPlayerId); + if (message.hitboxSizeX != null && Object.hasOwnProperty.call(message, "hitboxSizeX")) + writer.uint32(/* id 16, wireType 1 =*/129).double(message.hitboxSizeX); + if (message.hitboxSizeY != null && Object.hasOwnProperty.call(message, "hitboxSizeY")) + writer.uint32(/* id 17, wireType 1 =*/137).double(message.hitboxSizeY); + if (message.selfMoveforwardX != null && Object.hasOwnProperty.call(message, "selfMoveforwardX")) + writer.uint32(/* id 18, wireType 1 =*/145).double(message.selfMoveforwardX); + if (message.selfMoveforwardY != null && Object.hasOwnProperty.call(message, "selfMoveforwardY")) + writer.uint32(/* id 19, wireType 1 =*/153).double(message.selfMoveforwardY); return writer; }; @@ -4247,49 +4269,57 @@ $root.protos = (function() { break; } case 7: { - message.moveforward = $root.sharedprotos.Vec2D.decode(reader, reader.uint32()); - break; - } - case 8: { message.hitboxOffset = reader.double(); break; } - case 9: { - message.hitboxSize = $root.sharedprotos.Vec2D.decode(reader, reader.uint32()); - break; - } - case 10: { + case 8: { message.originatedRenderFrameId = reader.int32(); break; } - case 11: { + case 9: { message.hitStunFrames = reader.int32(); break; } - case 12: { + case 10: { message.blockStunFrames = reader.int32(); break; } - case 13: { + case 11: { message.pushback = reader.double(); break; } - case 14: { + case 12: { message.releaseTriggerType = reader.int32(); break; } - case 15: { + case 13: { message.damage = reader.int32(); break; } - case 16: { + case 14: { message.offenderJoinIndex = reader.int32(); break; } - case 17: { + case 15: { message.offenderPlayerId = reader.int32(); break; } + case 16: { + message.hitboxSizeX = reader.double(); + break; + } + case 17: { + message.hitboxSizeY = reader.double(); + break; + } + case 18: { + message.selfMoveforwardX = reader.double(); + break; + } + case 19: { + message.selfMoveforwardY = reader.double(); + break; + } default: reader.skipType(tag & 7); break; @@ -4343,19 +4373,9 @@ $root.protos = (function() { if (message.recoveryFramesOnHit != null && message.hasOwnProperty("recoveryFramesOnHit")) if (!$util.isInteger(message.recoveryFramesOnHit)) return "recoveryFramesOnHit: integer expected"; - if (message.moveforward != null && message.hasOwnProperty("moveforward")) { - var error = $root.sharedprotos.Vec2D.verify(message.moveforward); - if (error) - return "moveforward." + error; - } if (message.hitboxOffset != null && message.hasOwnProperty("hitboxOffset")) if (typeof message.hitboxOffset !== "number") return "hitboxOffset: number expected"; - if (message.hitboxSize != null && message.hasOwnProperty("hitboxSize")) { - var error = $root.sharedprotos.Vec2D.verify(message.hitboxSize); - if (error) - return "hitboxSize." + error; - } if (message.originatedRenderFrameId != null && message.hasOwnProperty("originatedRenderFrameId")) if (!$util.isInteger(message.originatedRenderFrameId)) return "originatedRenderFrameId: integer expected"; @@ -4380,6 +4400,18 @@ $root.protos = (function() { if (message.offenderPlayerId != null && message.hasOwnProperty("offenderPlayerId")) if (!$util.isInteger(message.offenderPlayerId)) return "offenderPlayerId: integer expected"; + if (message.hitboxSizeX != null && message.hasOwnProperty("hitboxSizeX")) + if (typeof message.hitboxSizeX !== "number") + return "hitboxSizeX: number expected"; + if (message.hitboxSizeY != null && message.hasOwnProperty("hitboxSizeY")) + if (typeof message.hitboxSizeY !== "number") + return "hitboxSizeY: number expected"; + if (message.selfMoveforwardX != null && message.hasOwnProperty("selfMoveforwardX")) + if (typeof message.selfMoveforwardX !== "number") + return "selfMoveforwardX: number expected"; + if (message.selfMoveforwardY != null && message.hasOwnProperty("selfMoveforwardY")) + if (typeof message.selfMoveforwardY !== "number") + return "selfMoveforwardY: number expected"; return null; }; @@ -4407,18 +4439,8 @@ $root.protos = (function() { message.recoveryFramesOnBlock = object.recoveryFramesOnBlock | 0; if (object.recoveryFramesOnHit != null) message.recoveryFramesOnHit = object.recoveryFramesOnHit | 0; - if (object.moveforward != null) { - if (typeof object.moveforward !== "object") - throw TypeError(".protos.MeleeBullet.moveforward: object expected"); - message.moveforward = $root.sharedprotos.Vec2D.fromObject(object.moveforward); - } if (object.hitboxOffset != null) message.hitboxOffset = Number(object.hitboxOffset); - if (object.hitboxSize != null) { - if (typeof object.hitboxSize !== "object") - throw TypeError(".protos.MeleeBullet.hitboxSize: object expected"); - message.hitboxSize = $root.sharedprotos.Vec2D.fromObject(object.hitboxSize); - } if (object.originatedRenderFrameId != null) message.originatedRenderFrameId = object.originatedRenderFrameId | 0; if (object.hitStunFrames != null) @@ -4435,6 +4457,14 @@ $root.protos = (function() { message.offenderJoinIndex = object.offenderJoinIndex | 0; if (object.offenderPlayerId != null) message.offenderPlayerId = object.offenderPlayerId | 0; + if (object.hitboxSizeX != null) + message.hitboxSizeX = Number(object.hitboxSizeX); + if (object.hitboxSizeY != null) + message.hitboxSizeY = Number(object.hitboxSizeY); + if (object.selfMoveforwardX != null) + message.selfMoveforwardX = Number(object.selfMoveforwardX); + if (object.selfMoveforwardY != null) + message.selfMoveforwardY = Number(object.selfMoveforwardY); return message; }; @@ -4458,9 +4488,7 @@ $root.protos = (function() { object.recoveryFrames = 0; object.recoveryFramesOnBlock = 0; object.recoveryFramesOnHit = 0; - object.moveforward = null; object.hitboxOffset = 0; - object.hitboxSize = null; object.originatedRenderFrameId = 0; object.hitStunFrames = 0; object.blockStunFrames = 0; @@ -4469,6 +4497,10 @@ $root.protos = (function() { object.damage = 0; object.offenderJoinIndex = 0; object.offenderPlayerId = 0; + object.hitboxSizeX = 0; + object.hitboxSizeY = 0; + object.selfMoveforwardX = 0; + object.selfMoveforwardY = 0; } if (message.battleLocalId != null && message.hasOwnProperty("battleLocalId")) object.battleLocalId = message.battleLocalId; @@ -4482,12 +4514,8 @@ $root.protos = (function() { object.recoveryFramesOnBlock = message.recoveryFramesOnBlock; if (message.recoveryFramesOnHit != null && message.hasOwnProperty("recoveryFramesOnHit")) object.recoveryFramesOnHit = message.recoveryFramesOnHit; - if (message.moveforward != null && message.hasOwnProperty("moveforward")) - object.moveforward = $root.sharedprotos.Vec2D.toObject(message.moveforward, options); if (message.hitboxOffset != null && message.hasOwnProperty("hitboxOffset")) object.hitboxOffset = options.json && !isFinite(message.hitboxOffset) ? String(message.hitboxOffset) : message.hitboxOffset; - if (message.hitboxSize != null && message.hasOwnProperty("hitboxSize")) - object.hitboxSize = $root.sharedprotos.Vec2D.toObject(message.hitboxSize, options); if (message.originatedRenderFrameId != null && message.hasOwnProperty("originatedRenderFrameId")) object.originatedRenderFrameId = message.originatedRenderFrameId; if (message.hitStunFrames != null && message.hasOwnProperty("hitStunFrames")) @@ -4504,6 +4532,14 @@ $root.protos = (function() { object.offenderJoinIndex = message.offenderJoinIndex; if (message.offenderPlayerId != null && message.hasOwnProperty("offenderPlayerId")) object.offenderPlayerId = message.offenderPlayerId; + if (message.hitboxSizeX != null && message.hasOwnProperty("hitboxSizeX")) + object.hitboxSizeX = options.json && !isFinite(message.hitboxSizeX) ? String(message.hitboxSizeX) : message.hitboxSizeX; + if (message.hitboxSizeY != null && message.hasOwnProperty("hitboxSizeY")) + object.hitboxSizeY = options.json && !isFinite(message.hitboxSizeY) ? String(message.hitboxSizeY) : message.hitboxSizeY; + if (message.selfMoveforwardX != null && message.hasOwnProperty("selfMoveforwardX")) + object.selfMoveforwardX = options.json && !isFinite(message.selfMoveforwardX) ? String(message.selfMoveforwardX) : message.selfMoveforwardX; + if (message.selfMoveforwardY != null && message.hasOwnProperty("selfMoveforwardY")) + object.selfMoveforwardY = options.json && !isFinite(message.selfMoveforwardY) ? String(message.selfMoveforwardY) : message.selfMoveforwardY; return object; }; @@ -4543,8 +4579,6 @@ $root.protos = (function() { * @memberof protos * @interface IBattleColliderInfo * @property {string|null} [stageName] BattleColliderInfo stageName - * @property {Object.|null} [strToVec2DListMap] BattleColliderInfo strToVec2DListMap - * @property {Object.|null} [strToPolygon2DListMap] BattleColliderInfo strToPolygon2DListMap * @property {number|null} [stageDiscreteW] BattleColliderInfo stageDiscreteW * @property {number|null} [stageDiscreteH] BattleColliderInfo stageDiscreteH * @property {number|null} [stageTileW] BattleColliderInfo stageTileW @@ -4567,12 +4601,13 @@ $root.protos = (function() { * @property {number|null} [virtualGridToWorldRatio] BattleColliderInfo virtualGridToWorldRatio * @property {number|null} [spAtkLookupFrames] BattleColliderInfo spAtkLookupFrames * @property {number|null} [renderCacheSize] BattleColliderInfo renderCacheSize - * @property {Object.|null} [meleeSkillConfig] BattleColliderInfo meleeSkillConfig * @property {number|null} [snapIntoPlatformOverlap] BattleColliderInfo snapIntoPlatformOverlap * @property {number|null} [snapIntoPlatformThreshold] BattleColliderInfo snapIntoPlatformThreshold * @property {number|null} [jumpingInitVelY] BattleColliderInfo jumpingInitVelY * @property {number|null} [gravityX] BattleColliderInfo gravityX * @property {number|null} [gravityY] BattleColliderInfo gravityY + * @property {number|null} [collisionMinStep] BattleColliderInfo collisionMinStep + * @property {boolean|null} [frameDataLoggingEnabled] BattleColliderInfo frameDataLoggingEnabled */ /** @@ -4584,9 +4619,6 @@ $root.protos = (function() { * @param {protos.IBattleColliderInfo=} [properties] Properties to set */ function BattleColliderInfo(properties) { - this.strToVec2DListMap = {}; - this.strToPolygon2DListMap = {}; - this.meleeSkillConfig = {}; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -4601,22 +4633,6 @@ $root.protos = (function() { */ BattleColliderInfo.prototype.stageName = ""; - /** - * BattleColliderInfo strToVec2DListMap. - * @member {Object.} strToVec2DListMap - * @memberof protos.BattleColliderInfo - * @instance - */ - BattleColliderInfo.prototype.strToVec2DListMap = $util.emptyObject; - - /** - * BattleColliderInfo strToPolygon2DListMap. - * @member {Object.} strToPolygon2DListMap - * @memberof protos.BattleColliderInfo - * @instance - */ - BattleColliderInfo.prototype.strToPolygon2DListMap = $util.emptyObject; - /** * BattleColliderInfo stageDiscreteW. * @member {number} stageDiscreteW @@ -4793,14 +4809,6 @@ $root.protos = (function() { */ BattleColliderInfo.prototype.renderCacheSize = 0; - /** - * BattleColliderInfo meleeSkillConfig. - * @member {Object.} meleeSkillConfig - * @memberof protos.BattleColliderInfo - * @instance - */ - BattleColliderInfo.prototype.meleeSkillConfig = $util.emptyObject; - /** * BattleColliderInfo snapIntoPlatformOverlap. * @member {number} snapIntoPlatformOverlap @@ -4841,6 +4849,22 @@ $root.protos = (function() { */ BattleColliderInfo.prototype.gravityY = 0; + /** + * BattleColliderInfo collisionMinStep. + * @member {number} collisionMinStep + * @memberof protos.BattleColliderInfo + * @instance + */ + BattleColliderInfo.prototype.collisionMinStep = 0; + + /** + * BattleColliderInfo frameDataLoggingEnabled. + * @member {boolean} frameDataLoggingEnabled + * @memberof protos.BattleColliderInfo + * @instance + */ + BattleColliderInfo.prototype.frameDataLoggingEnabled = false; + /** * Creates a new BattleColliderInfo instance using the specified properties. * @function create @@ -4867,75 +4891,64 @@ $root.protos = (function() { writer = $Writer.create(); if (message.stageName != null && Object.hasOwnProperty.call(message, "stageName")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.stageName); - if (message.strToVec2DListMap != null && Object.hasOwnProperty.call(message, "strToVec2DListMap")) - for (var keys = Object.keys(message.strToVec2DListMap), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 2, wireType 2 =*/18).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); - $root.sharedprotos.Vec2DList.encode(message.strToVec2DListMap[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); - } - if (message.strToPolygon2DListMap != null && Object.hasOwnProperty.call(message, "strToPolygon2DListMap")) - for (var keys = Object.keys(message.strToPolygon2DListMap), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 3, wireType 2 =*/26).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]); - $root.sharedprotos.Polygon2DList.encode(message.strToPolygon2DListMap[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); - } if (message.stageDiscreteW != null && Object.hasOwnProperty.call(message, "stageDiscreteW")) - writer.uint32(/* id 4, wireType 0 =*/32).int32(message.stageDiscreteW); + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.stageDiscreteW); if (message.stageDiscreteH != null && Object.hasOwnProperty.call(message, "stageDiscreteH")) - writer.uint32(/* id 5, wireType 0 =*/40).int32(message.stageDiscreteH); + writer.uint32(/* id 3, wireType 0 =*/24).int32(message.stageDiscreteH); if (message.stageTileW != null && Object.hasOwnProperty.call(message, "stageTileW")) - writer.uint32(/* id 6, wireType 0 =*/48).int32(message.stageTileW); + writer.uint32(/* id 4, wireType 0 =*/32).int32(message.stageTileW); if (message.stageTileH != null && Object.hasOwnProperty.call(message, "stageTileH")) - writer.uint32(/* id 7, wireType 0 =*/56).int32(message.stageTileH); + writer.uint32(/* id 5, wireType 0 =*/40).int32(message.stageTileH); if (message.intervalToPing != null && Object.hasOwnProperty.call(message, "intervalToPing")) - writer.uint32(/* id 8, wireType 0 =*/64).int32(message.intervalToPing); + writer.uint32(/* id 6, wireType 0 =*/48).int32(message.intervalToPing); if (message.willKickIfInactiveFor != null && Object.hasOwnProperty.call(message, "willKickIfInactiveFor")) - writer.uint32(/* id 9, wireType 0 =*/72).int32(message.willKickIfInactiveFor); + writer.uint32(/* id 7, wireType 0 =*/56).int32(message.willKickIfInactiveFor); if (message.boundRoomId != null && Object.hasOwnProperty.call(message, "boundRoomId")) - writer.uint32(/* id 10, wireType 0 =*/80).int32(message.boundRoomId); + writer.uint32(/* id 8, wireType 0 =*/64).int32(message.boundRoomId); if (message.battleDurationFrames != null && Object.hasOwnProperty.call(message, "battleDurationFrames")) - writer.uint32(/* id 12, wireType 0 =*/96).int32(message.battleDurationFrames); + writer.uint32(/* id 9, wireType 0 =*/72).int32(message.battleDurationFrames); if (message.battleDurationNanos != null && Object.hasOwnProperty.call(message, "battleDurationNanos")) - writer.uint32(/* id 13, wireType 0 =*/104).int64(message.battleDurationNanos); + writer.uint32(/* id 10, wireType 0 =*/80).int64(message.battleDurationNanos); if (message.serverFps != null && Object.hasOwnProperty.call(message, "serverFps")) - writer.uint32(/* id 14, wireType 0 =*/112).int32(message.serverFps); + writer.uint32(/* id 11, wireType 0 =*/88).int32(message.serverFps); if (message.inputDelayFrames != null && Object.hasOwnProperty.call(message, "inputDelayFrames")) - writer.uint32(/* id 15, wireType 0 =*/120).int32(message.inputDelayFrames); + writer.uint32(/* id 12, wireType 0 =*/96).int32(message.inputDelayFrames); if (message.inputScaleFrames != null && Object.hasOwnProperty.call(message, "inputScaleFrames")) - writer.uint32(/* id 16, wireType 0 =*/128).uint32(message.inputScaleFrames); + writer.uint32(/* id 13, wireType 0 =*/104).uint32(message.inputScaleFrames); if (message.nstDelayFrames != null && Object.hasOwnProperty.call(message, "nstDelayFrames")) - writer.uint32(/* id 17, wireType 0 =*/136).int32(message.nstDelayFrames); + writer.uint32(/* id 14, wireType 0 =*/112).int32(message.nstDelayFrames); if (message.inputFrameUpsyncDelayTolerance != null && Object.hasOwnProperty.call(message, "inputFrameUpsyncDelayTolerance")) - writer.uint32(/* id 18, wireType 0 =*/144).int32(message.inputFrameUpsyncDelayTolerance); + writer.uint32(/* id 15, wireType 0 =*/120).int32(message.inputFrameUpsyncDelayTolerance); if (message.maxChasingRenderFramesPerUpdate != null && Object.hasOwnProperty.call(message, "maxChasingRenderFramesPerUpdate")) - writer.uint32(/* id 19, wireType 0 =*/152).int32(message.maxChasingRenderFramesPerUpdate); + writer.uint32(/* id 16, wireType 0 =*/128).int32(message.maxChasingRenderFramesPerUpdate); if (message.playerBattleState != null && Object.hasOwnProperty.call(message, "playerBattleState")) - writer.uint32(/* id 20, wireType 0 =*/160).int32(message.playerBattleState); + writer.uint32(/* id 17, wireType 0 =*/136).int32(message.playerBattleState); if (message.rollbackEstimatedDtMillis != null && Object.hasOwnProperty.call(message, "rollbackEstimatedDtMillis")) - writer.uint32(/* id 21, wireType 1 =*/169).double(message.rollbackEstimatedDtMillis); + writer.uint32(/* id 18, wireType 1 =*/145).double(message.rollbackEstimatedDtMillis); if (message.rollbackEstimatedDtNanos != null && Object.hasOwnProperty.call(message, "rollbackEstimatedDtNanos")) - writer.uint32(/* id 22, wireType 0 =*/176).int64(message.rollbackEstimatedDtNanos); + writer.uint32(/* id 19, wireType 0 =*/152).int64(message.rollbackEstimatedDtNanos); if (message.worldToVirtualGridRatio != null && Object.hasOwnProperty.call(message, "worldToVirtualGridRatio")) - writer.uint32(/* id 23, wireType 1 =*/185).double(message.worldToVirtualGridRatio); + writer.uint32(/* id 20, wireType 1 =*/161).double(message.worldToVirtualGridRatio); if (message.virtualGridToWorldRatio != null && Object.hasOwnProperty.call(message, "virtualGridToWorldRatio")) - writer.uint32(/* id 24, wireType 1 =*/193).double(message.virtualGridToWorldRatio); + writer.uint32(/* id 21, wireType 1 =*/169).double(message.virtualGridToWorldRatio); if (message.spAtkLookupFrames != null && Object.hasOwnProperty.call(message, "spAtkLookupFrames")) - writer.uint32(/* id 25, wireType 0 =*/200).int32(message.spAtkLookupFrames); + writer.uint32(/* id 22, wireType 0 =*/176).int32(message.spAtkLookupFrames); if (message.renderCacheSize != null && Object.hasOwnProperty.call(message, "renderCacheSize")) - writer.uint32(/* id 26, wireType 0 =*/208).int32(message.renderCacheSize); - if (message.meleeSkillConfig != null && Object.hasOwnProperty.call(message, "meleeSkillConfig")) - for (var keys = Object.keys(message.meleeSkillConfig), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 27, wireType 2 =*/218).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); - $root.protos.MeleeBullet.encode(message.meleeSkillConfig[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); - } + writer.uint32(/* id 23, wireType 0 =*/184).int32(message.renderCacheSize); if (message.snapIntoPlatformOverlap != null && Object.hasOwnProperty.call(message, "snapIntoPlatformOverlap")) - writer.uint32(/* id 28, wireType 1 =*/225).double(message.snapIntoPlatformOverlap); + writer.uint32(/* id 24, wireType 1 =*/193).double(message.snapIntoPlatformOverlap); if (message.snapIntoPlatformThreshold != null && Object.hasOwnProperty.call(message, "snapIntoPlatformThreshold")) - writer.uint32(/* id 29, wireType 1 =*/233).double(message.snapIntoPlatformThreshold); + writer.uint32(/* id 25, wireType 1 =*/201).double(message.snapIntoPlatformThreshold); if (message.jumpingInitVelY != null && Object.hasOwnProperty.call(message, "jumpingInitVelY")) - writer.uint32(/* id 30, wireType 0 =*/240).int32(message.jumpingInitVelY); + writer.uint32(/* id 26, wireType 0 =*/208).int32(message.jumpingInitVelY); if (message.gravityX != null && Object.hasOwnProperty.call(message, "gravityX")) - writer.uint32(/* id 31, wireType 0 =*/248).int32(message.gravityX); + writer.uint32(/* id 27, wireType 0 =*/216).int32(message.gravityX); if (message.gravityY != null && Object.hasOwnProperty.call(message, "gravityY")) - writer.uint32(/* id 32, wireType 0 =*/256).int32(message.gravityY); + writer.uint32(/* id 28, wireType 0 =*/224).int32(message.gravityY); + if (message.collisionMinStep != null && Object.hasOwnProperty.call(message, "collisionMinStep")) + writer.uint32(/* id 29, wireType 0 =*/232).int32(message.collisionMinStep); + if (message.frameDataLoggingEnabled != null && Object.hasOwnProperty.call(message, "frameDataLoggingEnabled")) + writer.uint32(/* id 999, wireType 0 =*/7992).bool(message.frameDataLoggingEnabled); return writer; }; @@ -4966,7 +4979,7 @@ $root.protos = (function() { BattleColliderInfo.decode = function decode(reader, length) { if (!(reader instanceof $Reader)) reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.protos.BattleColliderInfo(), key, value; + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.protos.BattleColliderInfo(); while (reader.pos < end) { var tag = reader.uint32(); switch (tag >>> 3) { @@ -4975,182 +4988,121 @@ $root.protos = (function() { break; } case 2: { - if (message.strToVec2DListMap === $util.emptyObject) - message.strToVec2DListMap = {}; - var end2 = reader.uint32() + reader.pos; - key = ""; - value = null; - while (reader.pos < end2) { - var tag2 = reader.uint32(); - switch (tag2 >>> 3) { - case 1: - key = reader.string(); - break; - case 2: - value = $root.sharedprotos.Vec2DList.decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag2 & 7); - break; - } - } - message.strToVec2DListMap[key] = value; - break; - } - case 3: { - if (message.strToPolygon2DListMap === $util.emptyObject) - message.strToPolygon2DListMap = {}; - var end2 = reader.uint32() + reader.pos; - key = ""; - value = null; - while (reader.pos < end2) { - var tag2 = reader.uint32(); - switch (tag2 >>> 3) { - case 1: - key = reader.string(); - break; - case 2: - value = $root.sharedprotos.Polygon2DList.decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag2 & 7); - break; - } - } - message.strToPolygon2DListMap[key] = value; - break; - } - case 4: { message.stageDiscreteW = reader.int32(); break; } - case 5: { + case 3: { message.stageDiscreteH = reader.int32(); break; } - case 6: { + case 4: { message.stageTileW = reader.int32(); break; } - case 7: { + case 5: { message.stageTileH = reader.int32(); break; } - case 8: { + case 6: { message.intervalToPing = reader.int32(); break; } - case 9: { + case 7: { message.willKickIfInactiveFor = reader.int32(); break; } - case 10: { + case 8: { message.boundRoomId = reader.int32(); break; } - case 12: { + case 9: { message.battleDurationFrames = reader.int32(); break; } - case 13: { + case 10: { message.battleDurationNanos = reader.int64(); break; } - case 14: { + case 11: { message.serverFps = reader.int32(); break; } - case 15: { + case 12: { message.inputDelayFrames = reader.int32(); break; } - case 16: { + case 13: { message.inputScaleFrames = reader.uint32(); break; } - case 17: { + case 14: { message.nstDelayFrames = reader.int32(); break; } - case 18: { + case 15: { message.inputFrameUpsyncDelayTolerance = reader.int32(); break; } - case 19: { + case 16: { message.maxChasingRenderFramesPerUpdate = reader.int32(); break; } - case 20: { + case 17: { message.playerBattleState = reader.int32(); break; } - case 21: { + case 18: { message.rollbackEstimatedDtMillis = reader.double(); break; } - case 22: { + case 19: { message.rollbackEstimatedDtNanos = reader.int64(); break; } - case 23: { + case 20: { message.worldToVirtualGridRatio = reader.double(); break; } - case 24: { + case 21: { message.virtualGridToWorldRatio = reader.double(); break; } - case 25: { + case 22: { message.spAtkLookupFrames = reader.int32(); break; } - case 26: { + case 23: { message.renderCacheSize = reader.int32(); break; } - case 27: { - if (message.meleeSkillConfig === $util.emptyObject) - message.meleeSkillConfig = {}; - var end2 = reader.uint32() + reader.pos; - key = 0; - value = null; - while (reader.pos < end2) { - var tag2 = reader.uint32(); - switch (tag2 >>> 3) { - case 1: - key = reader.int32(); - break; - case 2: - value = $root.protos.MeleeBullet.decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag2 & 7); - break; - } - } - message.meleeSkillConfig[key] = value; - break; - } - case 28: { + case 24: { message.snapIntoPlatformOverlap = reader.double(); break; } - case 29: { + case 25: { message.snapIntoPlatformThreshold = reader.double(); break; } - case 30: { + case 26: { message.jumpingInitVelY = reader.int32(); break; } - case 31: { + case 27: { message.gravityX = reader.int32(); break; } - case 32: { + case 28: { message.gravityY = reader.int32(); break; } + case 29: { + message.collisionMinStep = reader.int32(); + break; + } + case 999: { + message.frameDataLoggingEnabled = reader.bool(); + break; + } default: reader.skipType(tag & 7); break; @@ -5189,26 +5141,6 @@ $root.protos = (function() { if (message.stageName != null && message.hasOwnProperty("stageName")) if (!$util.isString(message.stageName)) return "stageName: string expected"; - if (message.strToVec2DListMap != null && message.hasOwnProperty("strToVec2DListMap")) { - if (!$util.isObject(message.strToVec2DListMap)) - return "strToVec2DListMap: object expected"; - var key = Object.keys(message.strToVec2DListMap); - for (var i = 0; i < key.length; ++i) { - var error = $root.sharedprotos.Vec2DList.verify(message.strToVec2DListMap[key[i]]); - if (error) - return "strToVec2DListMap." + error; - } - } - if (message.strToPolygon2DListMap != null && message.hasOwnProperty("strToPolygon2DListMap")) { - if (!$util.isObject(message.strToPolygon2DListMap)) - return "strToPolygon2DListMap: object expected"; - var key = Object.keys(message.strToPolygon2DListMap); - for (var i = 0; i < key.length; ++i) { - var error = $root.sharedprotos.Polygon2DList.verify(message.strToPolygon2DListMap[key[i]]); - if (error) - return "strToPolygon2DListMap." + error; - } - } if (message.stageDiscreteW != null && message.hasOwnProperty("stageDiscreteW")) if (!$util.isInteger(message.stageDiscreteW)) return "stageDiscreteW: integer expected"; @@ -5275,20 +5207,6 @@ $root.protos = (function() { if (message.renderCacheSize != null && message.hasOwnProperty("renderCacheSize")) if (!$util.isInteger(message.renderCacheSize)) return "renderCacheSize: integer expected"; - if (message.meleeSkillConfig != null && message.hasOwnProperty("meleeSkillConfig")) { - if (!$util.isObject(message.meleeSkillConfig)) - return "meleeSkillConfig: object expected"; - var key = Object.keys(message.meleeSkillConfig); - for (var i = 0; i < key.length; ++i) { - if (!$util.key32Re.test(key[i])) - return "meleeSkillConfig: integer key{k:int32} expected"; - { - var error = $root.protos.MeleeBullet.verify(message.meleeSkillConfig[key[i]]); - if (error) - return "meleeSkillConfig." + error; - } - } - } if (message.snapIntoPlatformOverlap != null && message.hasOwnProperty("snapIntoPlatformOverlap")) if (typeof message.snapIntoPlatformOverlap !== "number") return "snapIntoPlatformOverlap: number expected"; @@ -5304,6 +5222,12 @@ $root.protos = (function() { if (message.gravityY != null && message.hasOwnProperty("gravityY")) if (!$util.isInteger(message.gravityY)) return "gravityY: integer expected"; + if (message.collisionMinStep != null && message.hasOwnProperty("collisionMinStep")) + if (!$util.isInteger(message.collisionMinStep)) + return "collisionMinStep: integer expected"; + if (message.frameDataLoggingEnabled != null && message.hasOwnProperty("frameDataLoggingEnabled")) + if (typeof message.frameDataLoggingEnabled !== "boolean") + return "frameDataLoggingEnabled: boolean expected"; return null; }; @@ -5321,26 +5245,6 @@ $root.protos = (function() { var message = new $root.protos.BattleColliderInfo(); if (object.stageName != null) message.stageName = String(object.stageName); - if (object.strToVec2DListMap) { - if (typeof object.strToVec2DListMap !== "object") - throw TypeError(".protos.BattleColliderInfo.strToVec2DListMap: object expected"); - message.strToVec2DListMap = {}; - for (var keys = Object.keys(object.strToVec2DListMap), i = 0; i < keys.length; ++i) { - if (typeof object.strToVec2DListMap[keys[i]] !== "object") - throw TypeError(".protos.BattleColliderInfo.strToVec2DListMap: object expected"); - message.strToVec2DListMap[keys[i]] = $root.sharedprotos.Vec2DList.fromObject(object.strToVec2DListMap[keys[i]]); - } - } - if (object.strToPolygon2DListMap) { - if (typeof object.strToPolygon2DListMap !== "object") - throw TypeError(".protos.BattleColliderInfo.strToPolygon2DListMap: object expected"); - message.strToPolygon2DListMap = {}; - for (var keys = Object.keys(object.strToPolygon2DListMap), i = 0; i < keys.length; ++i) { - if (typeof object.strToPolygon2DListMap[keys[i]] !== "object") - throw TypeError(".protos.BattleColliderInfo.strToPolygon2DListMap: object expected"); - message.strToPolygon2DListMap[keys[i]] = $root.sharedprotos.Polygon2DList.fromObject(object.strToPolygon2DListMap[keys[i]]); - } - } if (object.stageDiscreteW != null) message.stageDiscreteW = object.stageDiscreteW | 0; if (object.stageDiscreteH != null) @@ -5399,16 +5303,6 @@ $root.protos = (function() { message.spAtkLookupFrames = object.spAtkLookupFrames | 0; if (object.renderCacheSize != null) message.renderCacheSize = object.renderCacheSize | 0; - if (object.meleeSkillConfig) { - if (typeof object.meleeSkillConfig !== "object") - throw TypeError(".protos.BattleColliderInfo.meleeSkillConfig: object expected"); - message.meleeSkillConfig = {}; - for (var keys = Object.keys(object.meleeSkillConfig), i = 0; i < keys.length; ++i) { - if (typeof object.meleeSkillConfig[keys[i]] !== "object") - throw TypeError(".protos.BattleColliderInfo.meleeSkillConfig: object expected"); - message.meleeSkillConfig[keys[i]] = $root.protos.MeleeBullet.fromObject(object.meleeSkillConfig[keys[i]]); - } - } if (object.snapIntoPlatformOverlap != null) message.snapIntoPlatformOverlap = Number(object.snapIntoPlatformOverlap); if (object.snapIntoPlatformThreshold != null) @@ -5419,6 +5313,10 @@ $root.protos = (function() { message.gravityX = object.gravityX | 0; if (object.gravityY != null) message.gravityY = object.gravityY | 0; + if (object.collisionMinStep != null) + message.collisionMinStep = object.collisionMinStep | 0; + if (object.frameDataLoggingEnabled != null) + message.frameDataLoggingEnabled = Boolean(object.frameDataLoggingEnabled); return message; }; @@ -5435,11 +5333,6 @@ $root.protos = (function() { if (!options) options = {}; var object = {}; - if (options.objects || options.defaults) { - object.strToVec2DListMap = {}; - object.strToPolygon2DListMap = {}; - object.meleeSkillConfig = {}; - } if (options.defaults) { object.stageName = ""; object.stageDiscreteW = 0; @@ -5477,20 +5370,11 @@ $root.protos = (function() { object.jumpingInitVelY = 0; object.gravityX = 0; object.gravityY = 0; + object.collisionMinStep = 0; + object.frameDataLoggingEnabled = false; } if (message.stageName != null && message.hasOwnProperty("stageName")) object.stageName = message.stageName; - var keys2; - if (message.strToVec2DListMap && (keys2 = Object.keys(message.strToVec2DListMap)).length) { - object.strToVec2DListMap = {}; - for (var j = 0; j < keys2.length; ++j) - object.strToVec2DListMap[keys2[j]] = $root.sharedprotos.Vec2DList.toObject(message.strToVec2DListMap[keys2[j]], options); - } - if (message.strToPolygon2DListMap && (keys2 = Object.keys(message.strToPolygon2DListMap)).length) { - object.strToPolygon2DListMap = {}; - for (var j = 0; j < keys2.length; ++j) - object.strToPolygon2DListMap[keys2[j]] = $root.sharedprotos.Polygon2DList.toObject(message.strToPolygon2DListMap[keys2[j]], options); - } if (message.stageDiscreteW != null && message.hasOwnProperty("stageDiscreteW")) object.stageDiscreteW = message.stageDiscreteW; if (message.stageDiscreteH != null && message.hasOwnProperty("stageDiscreteH")) @@ -5541,11 +5425,6 @@ $root.protos = (function() { object.spAtkLookupFrames = message.spAtkLookupFrames; if (message.renderCacheSize != null && message.hasOwnProperty("renderCacheSize")) object.renderCacheSize = message.renderCacheSize; - if (message.meleeSkillConfig && (keys2 = Object.keys(message.meleeSkillConfig)).length) { - object.meleeSkillConfig = {}; - for (var j = 0; j < keys2.length; ++j) - object.meleeSkillConfig[keys2[j]] = $root.protos.MeleeBullet.toObject(message.meleeSkillConfig[keys2[j]], options); - } if (message.snapIntoPlatformOverlap != null && message.hasOwnProperty("snapIntoPlatformOverlap")) object.snapIntoPlatformOverlap = options.json && !isFinite(message.snapIntoPlatformOverlap) ? String(message.snapIntoPlatformOverlap) : message.snapIntoPlatformOverlap; if (message.snapIntoPlatformThreshold != null && message.hasOwnProperty("snapIntoPlatformThreshold")) @@ -5556,6 +5435,10 @@ $root.protos = (function() { object.gravityX = message.gravityX; if (message.gravityY != null && message.hasOwnProperty("gravityY")) object.gravityY = message.gravityY; + if (message.collisionMinStep != null && message.hasOwnProperty("collisionMinStep")) + object.collisionMinStep = message.collisionMinStep; + if (message.frameDataLoggingEnabled != null && message.hasOwnProperty("frameDataLoggingEnabled")) + object.frameDataLoggingEnabled = message.frameDataLoggingEnabled; return object; }; @@ -5595,11 +5478,12 @@ $root.protos = (function() { * @memberof protos * @interface IRoomDownsyncFrame * @property {number|null} [id] RoomDownsyncFrame id - * @property {Object.|null} [players] RoomDownsyncFrame players + * @property {Array.|null} [playersArr] RoomDownsyncFrame playersArr * @property {number|Long|null} [countdownNanos] RoomDownsyncFrame countdownNanos * @property {Array.|null} [meleeBullets] RoomDownsyncFrame meleeBullets * @property {number|Long|null} [backendUnconfirmedMask] RoomDownsyncFrame backendUnconfirmedMask * @property {boolean|null} [shouldForceResync] RoomDownsyncFrame shouldForceResync + * @property {Object.|null} [playerOpPatternToSkillId] RoomDownsyncFrame playerOpPatternToSkillId */ /** @@ -5611,8 +5495,9 @@ $root.protos = (function() { * @param {protos.IRoomDownsyncFrame=} [properties] Properties to set */ function RoomDownsyncFrame(properties) { - this.players = {}; + this.playersArr = []; this.meleeBullets = []; + this.playerOpPatternToSkillId = {}; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -5628,12 +5513,12 @@ $root.protos = (function() { RoomDownsyncFrame.prototype.id = 0; /** - * RoomDownsyncFrame players. - * @member {Object.} players + * RoomDownsyncFrame playersArr. + * @member {Array.} playersArr * @memberof protos.RoomDownsyncFrame * @instance */ - RoomDownsyncFrame.prototype.players = $util.emptyObject; + RoomDownsyncFrame.prototype.playersArr = $util.emptyArray; /** * RoomDownsyncFrame countdownNanos. @@ -5667,6 +5552,14 @@ $root.protos = (function() { */ RoomDownsyncFrame.prototype.shouldForceResync = false; + /** + * RoomDownsyncFrame playerOpPatternToSkillId. + * @member {Object.} playerOpPatternToSkillId + * @memberof protos.RoomDownsyncFrame + * @instance + */ + RoomDownsyncFrame.prototype.playerOpPatternToSkillId = $util.emptyObject; + /** * Creates a new RoomDownsyncFrame instance using the specified properties. * @function create @@ -5693,11 +5586,9 @@ $root.protos = (function() { writer = $Writer.create(); if (message.id != null && Object.hasOwnProperty.call(message, "id")) writer.uint32(/* id 1, wireType 0 =*/8).int32(message.id); - if (message.players != null && Object.hasOwnProperty.call(message, "players")) - for (var keys = Object.keys(message.players), i = 0; i < keys.length; ++i) { - writer.uint32(/* id 2, wireType 2 =*/18).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]); - $root.protos.PlayerDownsync.encode(message.players[keys[i]], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim().ldelim(); - } + if (message.playersArr != null && message.playersArr.length) + for (var i = 0; i < message.playersArr.length; ++i) + $root.protos.PlayerDownsync.encode(message.playersArr[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); if (message.countdownNanos != null && Object.hasOwnProperty.call(message, "countdownNanos")) writer.uint32(/* id 3, wireType 0 =*/24).int64(message.countdownNanos); if (message.meleeBullets != null && message.meleeBullets.length) @@ -5707,6 +5598,9 @@ $root.protos = (function() { writer.uint32(/* id 5, wireType 0 =*/40).uint64(message.backendUnconfirmedMask); if (message.shouldForceResync != null && Object.hasOwnProperty.call(message, "shouldForceResync")) writer.uint32(/* id 6, wireType 0 =*/48).bool(message.shouldForceResync); + if (message.playerOpPatternToSkillId != null && Object.hasOwnProperty.call(message, "playerOpPatternToSkillId")) + for (var keys = Object.keys(message.playerOpPatternToSkillId), i = 0; i < keys.length; ++i) + writer.uint32(/* id 7, wireType 2 =*/58).fork().uint32(/* id 1, wireType 0 =*/8).int32(keys[i]).uint32(/* id 2, wireType 0 =*/16).int32(message.playerOpPatternToSkillId[keys[i]]).ldelim(); return writer; }; @@ -5746,26 +5640,9 @@ $root.protos = (function() { break; } case 2: { - if (message.players === $util.emptyObject) - message.players = {}; - var end2 = reader.uint32() + reader.pos; - key = 0; - value = null; - while (reader.pos < end2) { - var tag2 = reader.uint32(); - switch (tag2 >>> 3) { - case 1: - key = reader.int32(); - break; - case 2: - value = $root.protos.PlayerDownsync.decode(reader, reader.uint32()); - break; - default: - reader.skipType(tag2 & 7); - break; - } - } - message.players[key] = value; + if (!(message.playersArr && message.playersArr.length)) + message.playersArr = []; + message.playersArr.push($root.protos.PlayerDownsync.decode(reader, reader.uint32())); break; } case 3: { @@ -5786,6 +5663,29 @@ $root.protos = (function() { message.shouldForceResync = reader.bool(); break; } + case 7: { + if (message.playerOpPatternToSkillId === $util.emptyObject) + message.playerOpPatternToSkillId = {}; + var end2 = reader.uint32() + reader.pos; + key = 0; + value = 0; + while (reader.pos < end2) { + var tag2 = reader.uint32(); + switch (tag2 >>> 3) { + case 1: + key = reader.int32(); + break; + case 2: + value = reader.int32(); + break; + default: + reader.skipType(tag2 & 7); + break; + } + } + message.playerOpPatternToSkillId[key] = value; + break; + } default: reader.skipType(tag & 7); break; @@ -5824,18 +5724,13 @@ $root.protos = (function() { if (message.id != null && message.hasOwnProperty("id")) if (!$util.isInteger(message.id)) return "id: integer expected"; - if (message.players != null && message.hasOwnProperty("players")) { - if (!$util.isObject(message.players)) - return "players: object expected"; - var key = Object.keys(message.players); - for (var i = 0; i < key.length; ++i) { - if (!$util.key32Re.test(key[i])) - return "players: integer key{k:int32} expected"; - { - var error = $root.protos.PlayerDownsync.verify(message.players[key[i]]); - if (error) - return "players." + error; - } + if (message.playersArr != null && message.hasOwnProperty("playersArr")) { + if (!Array.isArray(message.playersArr)) + return "playersArr: array expected"; + for (var i = 0; i < message.playersArr.length; ++i) { + var error = $root.protos.PlayerDownsync.verify(message.playersArr[i]); + if (error) + return "playersArr." + error; } } if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos")) @@ -5856,6 +5751,17 @@ $root.protos = (function() { if (message.shouldForceResync != null && message.hasOwnProperty("shouldForceResync")) if (typeof message.shouldForceResync !== "boolean") return "shouldForceResync: boolean expected"; + if (message.playerOpPatternToSkillId != null && message.hasOwnProperty("playerOpPatternToSkillId")) { + if (!$util.isObject(message.playerOpPatternToSkillId)) + return "playerOpPatternToSkillId: object expected"; + var key = Object.keys(message.playerOpPatternToSkillId); + for (var i = 0; i < key.length; ++i) { + if (!$util.key32Re.test(key[i])) + return "playerOpPatternToSkillId: integer key{k:int32} expected"; + if (!$util.isInteger(message.playerOpPatternToSkillId[key[i]])) + return "playerOpPatternToSkillId: integer{k:int32} expected"; + } + } return null; }; @@ -5873,14 +5779,14 @@ $root.protos = (function() { var message = new $root.protos.RoomDownsyncFrame(); if (object.id != null) message.id = object.id | 0; - if (object.players) { - if (typeof object.players !== "object") - throw TypeError(".protos.RoomDownsyncFrame.players: object expected"); - message.players = {}; - for (var keys = Object.keys(object.players), i = 0; i < keys.length; ++i) { - if (typeof object.players[keys[i]] !== "object") - throw TypeError(".protos.RoomDownsyncFrame.players: object expected"); - message.players[keys[i]] = $root.protos.PlayerDownsync.fromObject(object.players[keys[i]]); + if (object.playersArr) { + if (!Array.isArray(object.playersArr)) + throw TypeError(".protos.RoomDownsyncFrame.playersArr: array expected"); + message.playersArr = []; + for (var i = 0; i < object.playersArr.length; ++i) { + if (typeof object.playersArr[i] !== "object") + throw TypeError(".protos.RoomDownsyncFrame.playersArr: object expected"); + message.playersArr[i] = $root.protos.PlayerDownsync.fromObject(object.playersArr[i]); } } if (object.countdownNanos != null) @@ -5913,6 +5819,13 @@ $root.protos = (function() { message.backendUnconfirmedMask = new $util.LongBits(object.backendUnconfirmedMask.low >>> 0, object.backendUnconfirmedMask.high >>> 0).toNumber(true); if (object.shouldForceResync != null) message.shouldForceResync = Boolean(object.shouldForceResync); + if (object.playerOpPatternToSkillId) { + if (typeof object.playerOpPatternToSkillId !== "object") + throw TypeError(".protos.RoomDownsyncFrame.playerOpPatternToSkillId: object expected"); + message.playerOpPatternToSkillId = {}; + for (var keys = Object.keys(object.playerOpPatternToSkillId), i = 0; i < keys.length; ++i) + message.playerOpPatternToSkillId[keys[i]] = object.playerOpPatternToSkillId[keys[i]] | 0; + } return message; }; @@ -5929,10 +5842,12 @@ $root.protos = (function() { if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) + if (options.arrays || options.defaults) { + object.playersArr = []; object.meleeBullets = []; + } if (options.objects || options.defaults) - object.players = {}; + object.playerOpPatternToSkillId = {}; if (options.defaults) { object.id = 0; if ($util.Long) { @@ -5949,11 +5864,10 @@ $root.protos = (function() { } if (message.id != null && message.hasOwnProperty("id")) object.id = message.id; - var keys2; - if (message.players && (keys2 = Object.keys(message.players)).length) { - object.players = {}; - for (var j = 0; j < keys2.length; ++j) - object.players[keys2[j]] = $root.protos.PlayerDownsync.toObject(message.players[keys2[j]], options); + if (message.playersArr && message.playersArr.length) { + object.playersArr = []; + for (var j = 0; j < message.playersArr.length; ++j) + object.playersArr[j] = $root.protos.PlayerDownsync.toObject(message.playersArr[j], options); } if (message.countdownNanos != null && message.hasOwnProperty("countdownNanos")) if (typeof message.countdownNanos === "number") @@ -5972,6 +5886,12 @@ $root.protos = (function() { object.backendUnconfirmedMask = options.longs === String ? $util.Long.prototype.toString.call(message.backendUnconfirmedMask) : options.longs === Number ? new $util.LongBits(message.backendUnconfirmedMask.low >>> 0, message.backendUnconfirmedMask.high >>> 0).toNumber(true) : message.backendUnconfirmedMask; if (message.shouldForceResync != null && message.hasOwnProperty("shouldForceResync")) object.shouldForceResync = message.shouldForceResync; + var keys2; + if (message.playerOpPatternToSkillId && (keys2 = Object.keys(message.playerOpPatternToSkillId)).length) { + object.playerOpPatternToSkillId = {}; + for (var j = 0; j < keys2.length; ++j) + object.playerOpPatternToSkillId[keys2[j]] = message.playerOpPatternToSkillId[keys2[j]]; + } return object; }; diff --git a/frontend/settings/builder.json b/frontend/settings/builder.json index 168d9c7..e85983b 100644 --- a/frontend/settings/builder.json +++ b/frontend/settings/builder.json @@ -17,7 +17,7 @@ }, "encryptJs": true, "excludeScenes": [ - "368b10b6-88fc-423c-9fcd-545d9fc673bd" + "8491a86c-bec9-4813-968a-128ca01639e0" ], "fb-instant-games": {}, "includeSDKBox": false, diff --git a/frontend/settings/project.json b/frontend/settings/project.json index 7d067aa..7aebfed 100644 --- a/frontend/settings/project.json +++ b/frontend/settings/project.json @@ -68,7 +68,7 @@ "shelter_z_reducer", "shelter" ], - "last-module-event-record-time": 1671346284377, + "last-module-event-record-time": 1672287736326, "simulator-orientation": false, "simulator-resolution": { "height": 640, diff --git a/jsexport/Makefile b/jsexport/Makefile new file mode 100644 index 0000000..2087ef6 --- /dev/null +++ b/jsexport/Makefile @@ -0,0 +1,28 @@ +PROJECTNAME=jsexport +ROOT_DIR=. +all: help +## Available proxies for downloading go modules are listed in "https://github.com/golang/go/wiki/Modules#how-do-i-use-vendoring-with-modules-is-vendoring-going-away". +#GOPROXY=https://mirrors.aliyun.com/goproxy +GOPROXY=https://goproxy.io + +serve: + gopherjs clean + gopherjs serve $(PROJECTNAME) + +build: + gopherjs build $(PROJECTNAME) + rm ../frontend/assets/plugin_scripts/jsexport.js && mv ./jsexport.js ../frontend/assets/plugin_scripts/jsexport.js + +build-min: + gopherjs build -m $(PROJECTNAME) + rm ../frontend/assets/plugin_scripts/jsexport.js && mv ./jsexport.js ../frontend/assets/plugin_scripts/jsexport.js + +.PHONY: help + +help: Makefile + @echo + @echo " Choose a command run:" + @echo + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + @echo + diff --git a/jsexport/README.md b/jsexport/README.md new file mode 100644 index 0000000..be0cc85 --- /dev/null +++ b/jsexport/README.md @@ -0,0 +1,7 @@ +GopherJs is supposed to be run by `go run`. + +If on-the-fly compilation is needed, run `gopherjs serve jsexport` and then visit `http://localhost:8080/jsexport.js` -- if 404 not found is responded, run `gopherjs build` to check syntax errors. + +See the `Makefile` for more options. + +Kindly note that the sources of the greate opensource projects [resolv](https://github.com/SolarLune/resolv) and [vector](https://github.com/quartercastle/vector) are copied and modified here to reduce the size of generated js codes, i.e. standard libs `fmt`, `error`, `pb`(including standard libs `sync` and `reflect`) are deliberately avoided from scratch. diff --git a/jsexport/battle/battle.go b/jsexport/battle/battle.go new file mode 100644 index 0000000..8d9aee4 --- /dev/null +++ b/jsexport/battle/battle.go @@ -0,0 +1,713 @@ +package battle + +import ( + "math" + "resolv" +) + +const ( + MAX_FLOAT64 = 1.7e+308 + COLLISION_PLAYER_INDEX_PREFIX = (1 << 17) + COLLISION_BARRIER_INDEX_PREFIX = (1 << 16) + COLLISION_BULLET_INDEX_PREFIX = (1 << 15) + + PATTERN_ID_UNABLE_TO_OP = -2 + PATTERN_ID_NO_OP = -1 +) + +// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged. +var DIRECTION_DECODER = [][]int32{ + {0, 0}, + {0, +2}, + {0, -2}, + {+2, 0}, + {-2, 0}, + {+1, +1}, + {-1, -1}, + {+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 ( + ATK_CHARACTER_STATE_IDLE1 = int32(0) + ATK_CHARACTER_STATE_WALKING = int32(1) + ATK_CHARACTER_STATE_ATK1 = int32(2) + ATK_CHARACTER_STATE_ATKED1 = int32(3) + ATK_CHARACTER_STATE_INAIR_IDLE1 = int32(4) + ATK_CHARACTER_STATE_INAIR_ATK1 = int32(5) + ATK_CHARACTER_STATE_INAIR_ATKED1 = int32(6) +) + +func ConvertToInputFrameId(renderFrameId int32, inputDelayFrames int32, inputScaleFrames uint32) int32 { + if renderFrameId < inputDelayFrames { + return 0 + } + return ((renderFrameId - inputDelayFrames) >> inputScaleFrames) +} + +func decodeInput(encodedInput uint64) *InputFrameDecoded { + encodedDirection := (encodedInput & uint64(15)) + btnALevel := int32((encodedInput >> 4) & 1) + btnBLevel := int32((encodedInput >> 5) & 1) + return &InputFrameDecoded{ + Dx: DIRECTION_DECODER[encodedDirection][0], + Dy: DIRECTION_DECODER[encodedDirection][1], + BtnALevel: btnALevel, + BtnBLevel: btnBLevel, + } +} + +type SatResult struct { + Overlap float64 + OverlapX float64 + OverlapY float64 + AContainedInB bool + BContainedInA bool + Axis resolv.Vector +} + +func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) { + origX, origY := playerShape.Position() + defer func() { + playerShape.SetPosition(origX, origY) + }() + playerShape.SetPosition(origX+oldDx, origY+oldDy) + + overlapResult := &SatResult{ + Overlap: 0, + OverlapX: 0, + OverlapY: 0, + AContainedInB: true, + BContainedInA: true, + Axis: resolv.Vector{0, 0}, + } + if overlapped := isPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped { + pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY + return true, pushbackX, pushbackY, overlapResult + } else { + return false, 0, 0, overlapResult + } +} + +func isPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool { + aCnt, bCnt := len(a.Points), len(b.Points) + // Single point case + if 1 == aCnt && 1 == bCnt { + if nil != result { + result.Overlap = 0 + } + return a.Points[0][0] == b.Points[0][0] && a.Points[0][1] == b.Points[0][1] + } + + if 1 < aCnt { + for _, axis := range a.SATAxes() { + if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { + return false + } + } + } + + if 1 < bCnt { + for _, axis := range b.SATAxes() { + if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { + return false + } + } + } + + return true +} + +func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e resolv.Vector, result *SatResult) bool { + /* + [WARNING] This function is deliberately made private, it shouldn't be used alone (i.e. not along the norms of a polygon), otherwise the pushbacks calculated would be meaningless. + + Consider the following example + a: { + anchor: [1337.19 1696.74] + points: [[0 0] [24 0] [24 24] [0 24]] + }, + b: { + anchor: [1277.72 1570.56] + points: [[642.57 319.16] [0 319.16] [5.73 0] [643.75 0.90]] + } + + e = (-2.98, 1.49).Unit() + */ + + var aStart, aEnd, bStart, bEnd float64 = MAX_FLOAT64, -MAX_FLOAT64, MAX_FLOAT64, -MAX_FLOAT64 + for _, p := range a.Points { + dot := (p[0]+a.X)*e[0] + (p[1]+a.Y)*e[1] + + if aStart > dot { + aStart = dot + } + + if aEnd < dot { + aEnd = dot + } + } + + for _, p := range b.Points { + dot := (p[0]+b.X)*e[0] + (p[1]+b.Y)*e[1] + + if bStart > dot { + bStart = dot + } + + if bEnd < dot { + bEnd = dot + } + } + + if aStart > bEnd || aEnd < bStart { + // Separated by unit vector "e" + return true + } + + if nil != result { + overlap := float64(0) + + if aStart < bStart { + result.AContainedInB = false + + if aEnd < bEnd { + overlap = aEnd - bStart + result.BContainedInA = false + } else { + option1 := aEnd - bStart + option2 := bEnd - aStart + if option1 < option2 { + overlap = option1 + } else { + overlap = -option2 + } + } + } else { + result.BContainedInA = false + + if aEnd > bEnd { + overlap = aStart - bEnd + result.AContainedInB = false + } else { + option1 := aEnd - bStart + option2 := bEnd - aStart + if option1 < option2 { + overlap = option1 + } else { + overlap = -option2 + } + } + } + + currentOverlap := result.Overlap + absoluteOverlap := overlap + if overlap < 0 { + absoluteOverlap = -overlap + } + + if (0 == result.Axis[0] && 0 == result.Axis[1]) || currentOverlap > absoluteOverlap { + var sign float64 = 1 + if overlap < 0 { + sign = -1 + } + + result.Overlap = absoluteOverlap + result.OverlapX = e[0] * sign + result.OverlapY = e[1] * sign + } + + result.Axis = e + } + + // the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated + return false +} + +func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) { + // [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. + var virtualGridX int32 = int32(math.Floor(wx * worldToVirtualGridRatio)) + var virtualGridY int32 = int32(math.Floor(wy * worldToVirtualGridRatio)) + return virtualGridX, virtualGridY +} + +func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) { + // No loss of precision + var wx float64 = float64(vx) * virtualGridToWorldRatio + var wy float64 = float64(vy) * virtualGridToWorldRatio + return wx, wy +} + +func WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + return wx - halfBoundingW - leftPadding + collisionSpaceOffsetX, wy - halfBoundingH - bottomPadding + collisionSpaceOffsetY +} + +func PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) { + 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) { + wx, wy := PolygonColliderBLToWorldPos(cx, cy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) + return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio) +} + +func VirtualGridToPolygonColliderBLPos(vx, vy int32, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) { + wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio) + return WorldToPolygonColliderBLPos(wx, wy, halfBoundingW, halfBoundingH, topPadding, bottomPadding, leftPadding, rightPadding, collisionSpaceOffsetX, collisionSpaceOffsetY) +} + +func calcHardPushbacksNorms(joinIndex int32, playerCollider *resolv.Object, playerShape *resolv.ConvexPolygon, snapIntoPlatformOverlap float64, pEffPushback *Vec2D) *[]Vec2D { + ret := make([]Vec2D, 0, 10) // no one would simultaneously have more than 5 hardPushbacks + collision := playerCollider.Check(0, 0) + if nil == collision { + return &ret + } + + //playerColliderCenterX, playerColliderCenterY := playerCollider.Center() + //fmt.Printf("joinIndex=%d calcHardPushbacksNorms has non-empty collision;playerColliderPos=(%.2f,%.2f)\n", joinIndex, playerColliderCenterX, playerColliderCenterY) + for _, obj := range collision.Objects { + isBarrier := false + switch obj.Data.(type) { + case *PlayerDownsync: + case *MeleeBullet: + default: + // By default it's a regular barrier, even if data is nil, note that Golang syntax of switch-case is kind of confusing, this "default" condition is met only if "!*PlayerDownsync && !*MeleeBullet". + isBarrier = true + } + + if !isBarrier { + continue + } + barrierShape := obj.Shape.(*resolv.ConvexPolygon) + overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, barrierShape) + if !overlapped { + continue + } + // ALWAY snap into hardPushbacks! + // [OverlapX, OverlapY] is the unit vector that points into the platform + pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap)*overlapResult.OverlapY + ret = append(ret, Vec2D{X: overlapResult.OverlapX, Y: overlapResult.OverlapY}) + pEffPushback.X += pushbackX + pEffPushback.Y += pushbackY + //fmt.Printf("joinIndex=%d calcHardPushbacksNorms found one hardpushback; immediatePushback=(%.2f,%.2f)\n", joinIndex, pushbackX, pushbackY) + } + return &ret +} + +func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, inputsBuffer *RingBuffer, inputDelayFrames int32, inputScaleFrames uint32) (int, bool, int32, int32) { + // returns (patternId, jumpedOrNot, effectiveDx, effectiveDy) + delayedInputFrameId := ConvertToInputFrameId(currRenderFrame.Id, inputDelayFrames, inputScaleFrames) + delayedInputFrameIdForPrevRdf := ConvertToInputFrameId(currRenderFrame.Id-1, inputDelayFrames, inputScaleFrames) + + if 0 >= delayedInputFrameId { + return PATTERN_ID_UNABLE_TO_OP, false, 0, 0 + } + + delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList + var delayedInputListForPrevRdf []uint64 = nil + if 0 < delayedInputFrameIdForPrevRdf { + delayedInputListForPrevRdf = inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync).InputList + } + + jumpedOrNot := false + joinIndex := currPlayerDownsync.JoinIndex + if 0 < currPlayerDownsync.FramesToRecover { + return PATTERN_ID_UNABLE_TO_OP, false, 0, 0 + } + decodedInput := decodeInput(delayedInputList[joinIndex-1]) + effDx, effDy := decodedInput.Dx, decodedInput.Dy + prevBtnALevel, prevBtnBLevel := int32(0), int32(0) + if nil != delayedInputListForPrevRdf { + prevDecodedInput := decodeInput(delayedInputListForPrevRdf[joinIndex-1]) + prevBtnALevel = prevDecodedInput.BtnALevel + prevBtnBLevel = prevDecodedInput.BtnBLevel + } + + if decodedInput.BtnBLevel > prevBtnBLevel { + characStateAlreadyInAir := false + if ATK_CHARACTER_STATE_INAIR_IDLE1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == currPlayerDownsync.CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == currPlayerDownsync.CharacterState { + characStateAlreadyInAir = true + } + characStateIsInterruptWaivable := false + 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 + if decodedInput.BtnALevel > prevBtnALevel { + patternId = 0 + effDx, effDy = 0, 0 // Most patterns/skills should not allow simultaneous movement + } + + return patternId, jumpedOrNot, effDx, effDy +} + +// [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 { + // [WARNING] On backend this function MUST BE called while "InputsBufferLock" is locked! + roomCapacity := len(currRenderFrame.PlayersArr) + nextRenderFramePlayers := make([]*PlayerDownsync, roomCapacity) + // Make a copy first + for i, currPlayerDownsync := range currRenderFrame.PlayersArr { + nextRenderFramePlayers[i] = &PlayerDownsync{ + Id: currPlayerDownsync.Id, + VirtualGridX: currPlayerDownsync.VirtualGridX, + VirtualGridY: currPlayerDownsync.VirtualGridY, + DirX: currPlayerDownsync.DirX, + DirY: currPlayerDownsync.DirY, + VelX: currPlayerDownsync.VelX, + VelY: currPlayerDownsync.VelY, + CharacterState: currPlayerDownsync.CharacterState, + InAir: true, + Speed: currPlayerDownsync.Speed, + BattleState: currPlayerDownsync.BattleState, + Score: currPlayerDownsync.Score, + Removed: currPlayerDownsync.Removed, + JoinIndex: currPlayerDownsync.JoinIndex, + FramesToRecover: currPlayerDownsync.FramesToRecover - 1, + Hp: currPlayerDownsync.Hp, + MaxHp: currPlayerDownsync.MaxHp, + } + if nextRenderFramePlayers[i].FramesToRecover < 0 { + nextRenderFramePlayers[i].FramesToRecover = 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? + effPushbacks := make([]Vec2D, roomCapacity) + hardPushbackNorms := make([]*[]Vec2D, roomCapacity) + + // 1. Process player inputs + for i, currPlayerDownsync := range currRenderFrame.PlayersArr { + thatPlayerInNextFrame := nextRenderFramePlayers[i] + patternId, jumpedOrNot, effDx, effDy := deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame, currRenderFrame, inputsBuffer, inputDelayFrames, inputScaleFrames) + if PATTERN_ID_UNABLE_TO_OP == patternId { + continue + } + + if jumpedOrNot { + thatPlayerInNextFrame.VelY = jumpingInitVelY + thatPlayerInNextFrame.VirtualGridY += jumpingInitVelY // Immediately gets out of any snapping + } + joinIndex := currPlayerDownsync.JoinIndex + if PATTERN_ID_NO_OP != patternId { + if skillId, existent := playerOpPatternToSkillId[(int(joinIndex)< currRenderFrame.Id) { + offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1] + + xfac := float64(1.0) // By now, straight Punch offset doesn't respect "y-axis" + if 0 > offender.DirX { + xfac = float64(-1.0) + } + offenderWx, offenderWy := VirtualGridToWorldPos(offender.VirtualGridX, offender.VirtualGridY, virtualGridToWorldRatio) + bulletWx, bulletWy := offenderWx+xfac*meleeBullet.HitboxOffset, offenderWy + newBulletCollider := GenerateRectCollider(bulletWx, bulletWy, meleeBullet.HitboxSizeX, meleeBullet.HitboxSizeY, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, collisionSpaceOffsetX, collisionSpaceOffsetY, meleeBullet, "MeleeBullet") + collisionSys.Add(newBulletCollider) + bulletColliders = append(bulletColliders, newBulletCollider) + } else { + nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet) + } + } + + // 4. Calc pushbacks for each player (after its movement) w/o bullets + for i, currPlayerDownsync := range currRenderFrame.PlayersArr { + joinIndex := currPlayerDownsync.JoinIndex + collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex + playerCollider := collisionSysMap[collisionPlayerIndex] + playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) + hardPushbackNorms[joinIndex-1] = calcHardPushbacksNorms(joinIndex, playerCollider, playerShape, snapIntoPlatformOverlap, &(effPushbacks[joinIndex-1])) + thatPlayerInNextFrame := nextRenderFramePlayers[i] + landedOnGravityPushback := false + if collision := playerCollider.Check(0, 0); nil != collision { + for _, obj := range collision.Objects { + isBarrier, isAnotherPlayer, isBullet := false, false, false + switch obj.Data.(type) { + case *PlayerDownsync: + isAnotherPlayer = true + case *MeleeBullet: + isBullet = true + default: + // By default it's a regular barrier, even if data is nil + isBarrier = true + } + if isBullet { + // ignore bullets for this step + continue + } + bShape := obj.Shape.(*resolv.ConvexPolygon) + overlapped, pushbackX, pushbackY, overlapResult := CalcPushbacks(0, 0, playerShape, bShape) + if !overlapped { + continue + } + normAlignmentWithGravity := (overlapResult.OverlapX*float64(0) + overlapResult.OverlapY*float64(-1.0)) + 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. + pushbackX, pushbackY = (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapX, (overlapResult.Overlap-snapIntoPlatformOverlap*2)*overlapResult.OverlapY + } + for _, hardPushbackNorm := range *hardPushbackNorms[joinIndex-1] { + projectedMagnitude := pushbackX*hardPushbackNorm.X + pushbackY*hardPushbackNorm.Y + if isBarrier || (isAnotherPlayer && 0 > projectedMagnitude) { + pushbackX -= projectedMagnitude * hardPushbackNorm.X + pushbackY -= projectedMagnitude * hardPushbackNorm.Y + } + } + effPushbacks[joinIndex-1].X += pushbackX + effPushbacks[joinIndex-1].Y += pushbackY + + if snapIntoPlatformThreshold < normAlignmentWithGravity { + landedOnGravityPushback = true + //playerColliderCenterX, playerColliderCenterY := playerCollider.Center() + //fmt.Printf("joinIndex=%d landedOnGravityPushback\n{renderFrame.id: %d, isBarrier: %v, isAnotherPlayer: %v}\nhardPushbackNormsOfThisPlayer=%v, playerColliderPos=(%.2f,%.2f), immediatePushback={%.3f, %.3f}, effPushback={%.3f, %.3f}, overlapMag=%.4f\n", joinIndex, currRenderFrame.Id, isBarrier, isAnotherPlayer, *hardPushbackNorms[joinIndex-1], playerColliderCenterX, playerColliderCenterY, pushbackX, pushbackY, effPushbacks[joinIndex-1].X, effPushbacks[joinIndex-1].Y, overlapResult.Overlap) + } + } + } + if landedOnGravityPushback { + thatPlayerInNextFrame.InAir = false + if currPlayerDownsync.InAir && 0 >= currPlayerDownsync.VelY { + // fallStopping + thatPlayerInNextFrame.VelX = 0 + thatPlayerInNextFrame.VelY = 0 + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_IDLE1 + thatPlayerInNextFrame.FramesToRecover = 0 + } + } + if currPlayerDownsync.InAir { + oldNextCharacterState := thatPlayerInNextFrame.CharacterState + switch oldNextCharacterState { + case ATK_CHARACTER_STATE_IDLE1, ATK_CHARACTER_STATE_WALKING: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1 + case ATK_CHARACTER_STATE_ATK1: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1 + case ATK_CHARACTER_STATE_ATKED1: + thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1 + } + } + } + + // 5. Check bullet-anything collisions + for _, bulletCollider := range bulletColliders { + meleeBullet := bulletCollider.Data.(*MeleeBullet) + bulletShape := bulletCollider.Shape.(*resolv.ConvexPolygon) + collision := bulletCollider.Check(0, 0) + bulletCollider.Space.Remove(bulletCollider) // Make sure that the bulletCollider is always removed for each renderFrame + if nil == collision { + nextRenderFrameMeleeBullets = append(nextRenderFrameMeleeBullets, meleeBullet) + continue + } + offender := currRenderFrame.PlayersArr[meleeBullet.OffenderJoinIndex-1] + for _, obj := range collision.Objects { + defenderShape := obj.Shape.(*resolv.ConvexPolygon) + switch t := obj.Data.(type) { + case *PlayerDownsync: + if meleeBullet.OffenderPlayerId == t.Id { + continue + } + overlapped, _, _, _ := CalcPushbacks(0, 0, bulletShape, defenderShape) + if !overlapped { + 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 + } + } + + 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: + } + } + } + + // 6. Get players out of stuck barriers if there's any + for i, currPlayerDownsync := range currRenderFrame.PlayersArr { + joinIndex := currPlayerDownsync.JoinIndex + collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex + playerCollider := collisionSysMap[collisionPlayerIndex] + // Update "virtual grid position" + thatPlayerInNextFrame := nextRenderFramePlayers[i] + thatPlayerInNextFrame.VirtualGridX, thatPlayerInNextFrame.VirtualGridY = PolygonColliderBLToVirtualGridPos(playerCollider.X-effPushbacks[joinIndex-1].X, playerCollider.Y-effPushbacks[joinIndex-1].Y, playerCollider.W*0.5, playerCollider.H*0.5, 0, 0, 0, 0, collisionSpaceOffsetX, collisionSpaceOffsetY, worldToVirtualGridRatio) + } + + return &RoomDownsyncFrame{ + Id: currRenderFrame.Id + 1, + PlayersArr: nextRenderFramePlayers, + MeleeBullets: nextRenderFrameMeleeBullets, + } +} + +func GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object { + blX, blY := WorldToPolygonColliderBLPos(wx, wy, w*0.5, h*0.5, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY) + return generateRectColliderInCollisionSpace(blX, blY, leftPadding+w+rightPadding, bottomPadding+h+topPadding, data, tag) +} + +func generateRectColliderInCollisionSpace(blX, blY, w, h float64, data interface{}, tag string) *resolv.Object { + collider := resolv.NewObject(blX, blY, w, h, tag) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details + shape := resolv.NewRectangle(0, 0, w, h) + collider.SetShape(shape) + collider.Data = data + return collider +} + +func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *resolv.Object { + aligned := AlignPolygon2DToBoundingBox(unalignedSrc) + var w, h float64 = 0, 0 + + shape := resolv.NewConvexPolygon() + for i, pi := range aligned.Points { + for j, pj := range aligned.Points { + if i == j { + continue + } + if math.Abs(pj.X-pi.X) > w { + w = math.Abs(pj.X - pi.X) + } + if math.Abs(pj.Y-pi.Y) > h { + h = math.Abs(pj.Y - pi.Y) + } + } + } + + for i := 0; i < len(aligned.Points); i++ { + p := aligned.Points[i] + shape.AddPoints(p.X, p.Y) + } + + collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag) + collider.SetShape(shape) + collider.Data = data + + return collider +} + +func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D { + // Transform again to put "anchor" at the "bottom-left point (w.r.t. world space)" of the bounding box for "resolv" + boundingBoxBL := &Vec2D{ + X: MAX_FLOAT64, + Y: MAX_FLOAT64, + } + for _, p := range input.Points { + if p.X < boundingBoxBL.X { + boundingBoxBL.X = p.X + } + if p.Y < boundingBoxBL.Y { + boundingBoxBL.Y = p.Y + } + } + + // Now "input.Anchor" should move to "input.Anchor+boundingBoxBL", thus "boundingBoxBL" is also the value of the negative diff for all "input.Points" + output := &Polygon2D{ + Anchor: &Vec2D{ + X: input.Anchor.X + boundingBoxBL.X, + Y: input.Anchor.Y + boundingBoxBL.Y, + }, + Points: make([]*Vec2D, len(input.Points)), + } + + for i, p := range input.Points { + output.Points[i] = &Vec2D{ + X: p.X - boundingBoxBL.X, + Y: p.Y - boundingBoxBL.Y, + } + } + + return output +} diff --git a/battle_srv/models/ringbuf.go b/jsexport/battle/ringbuf.go similarity index 52% rename from battle_srv/models/ringbuf.go rename to jsexport/battle/ringbuf.go index 70ab871..477ae54 100644 --- a/battle_srv/models/ringbuf.go +++ b/jsexport/battle/ringbuf.go @@ -1,4 +1,10 @@ -package models +package battle + +const ( + RING_BUFF_CONSECUTIVE_SET = int32(0) + RING_BUFF_NON_CONSECUTIVE_SET = int32(1) + RING_BUFF_FAILED_TO_SET = int32(2) +) type RingBuffer struct { Ed int32 // write index, open index @@ -48,15 +54,15 @@ func (rb *RingBuffer) Pop() interface{} { return pItem } -func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} { - if 0 == rb.Cnt { - return nil +func (rb *RingBuffer) GetArrIdxByOffset(offsetFromSt int32) int32 { + if 0 == rb.Cnt || 0 > offsetFromSt { + return -1 } arrIdx := rb.St + offsetFromSt if rb.St < rb.Ed { // case#1: 0...st...ed...N-1 if rb.St <= arrIdx && arrIdx < rb.Ed { - return rb.Eles[arrIdx] + return arrIdx } } else { // if rb.St >= rb.Ed @@ -65,11 +71,19 @@ func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} { arrIdx -= rb.N } if arrIdx >= rb.St || arrIdx < rb.Ed { - return rb.Eles[arrIdx] + return arrIdx } } - return nil + return -1 +} + +func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} { + arrIdx := rb.GetArrIdxByOffset(offsetFromSt) + if -1 == arrIdx { + return nil + } + return rb.Eles[arrIdx] } func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} { @@ -78,3 +92,33 @@ func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} { } return rb.GetByOffset(frameId - rb.StFrameId) } + +// [WARNING] During a battle, frontend could receive non-consecutive frames (either renderFrame or inputFrame) due to resync, the buffer should handle these frames properly. +func (rb *RingBuffer) SetByFrameId(pItem interface{}, frameId int32) (int32, int32, int32) { + oldStFrameId, oldEdFrameId := rb.StFrameId, rb.EdFrameId + if frameId < oldStFrameId { + return RING_BUFF_FAILED_TO_SET, oldStFrameId, oldEdFrameId + } + // By now "rb.StFrameId <= frameId" + if oldEdFrameId > frameId { + arrIdx := rb.GetArrIdxByOffset(frameId - rb.StFrameId) + if -1 != arrIdx { + rb.Eles[arrIdx] = pItem + return RING_BUFF_CONSECUTIVE_SET, oldStFrameId, oldEdFrameId + } + } + + // By now "rb.EdFrameId <= frameId" + ret := RING_BUFF_CONSECUTIVE_SET + if oldEdFrameId < frameId { + rb.St, rb.Ed = 0, 0 + rb.StFrameId, rb.EdFrameId = frameId, frameId + rb.Cnt = 0 + ret = RING_BUFF_NON_CONSECUTIVE_SET + } + + // By now "rb.EdFrameId == frameId" + rb.Put(pItem) + + return ret, oldStFrameId, oldEdFrameId +} diff --git a/jsexport/battle/room_downsync_frame.go b/jsexport/battle/room_downsync_frame.go new file mode 100644 index 0000000..3bc65a7 --- /dev/null +++ b/jsexport/battle/room_downsync_frame.go @@ -0,0 +1,108 @@ +package battle + +// TODO: Replace all "int32", "int64", "uint32" and "uint64" with just "int" for better performance in JavaScript! Reference https://github.com/gopherjs/gopherjs#performance-tips + +type Vec2D struct { + X float64 + Y float64 +} + +type Polygon2D struct { + Anchor *Vec2D + Points []*Vec2D +} + +type PlayerDownsync struct { + Id int32 + VirtualGridX int32 + VirtualGridY int32 + DirX int32 + DirY int32 + VelX int32 + VelY int32 + Speed int32 + BattleState int32 + JoinIndex int32 + ColliderRadius float64 + Removed bool + Score int32 + LastMoveGmtMillis int32 + FramesToRecover int32 + Hp int32 + MaxHp int32 + CharacterState int32 + InAir bool +} + +type InputFrameDecoded struct { + Dx int32 + Dy int32 + BtnALevel int32 + BtnBLevel int32 +} + +type InputFrameUpsync struct { + InputFrameId int32 + Encoded uint64 +} + +type Barrier struct { + Boundary *Polygon2D +} + +type Bullet struct { + // for offender + BattleLocalId int32 + StartupFrames 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 + SelfMoveforwardY float64 + HitboxSizeX float64 + HitboxSizeY float64 +} + +type MeleeBullet struct { + Bullet +} + +type FireballBullet struct { + VirtualGridX int32 + VirtualGridY int32 + DirX int32 + DirY int32 + VelX int32 + VelY int32 + Speed int32 + Bullet +} + +type RoomDownsyncFrame struct { + Id int32 + PlayersArr []*PlayerDownsync + CountdownNanos int64 + MeleeBullets []*MeleeBullet + FireballBullets []*FireballBullet + BackendUnconfirmedMask uint64 + ShouldForceResync bool + PlayerOpPatternToSkillId map[int]int +} + +type InputFrameDownsync struct { + InputFrameId int32 + InputList []uint64 + ConfirmedList uint64 +} diff --git a/jsexport/go.mod b/jsexport/go.mod new file mode 100644 index 0000000..3577816 --- /dev/null +++ b/jsexport/go.mod @@ -0,0 +1,12 @@ +module jsexport + +go 1.18 + +require ( + github.com/gopherjs/gopherjs v1.18.0-beta1 + resolv v0.0.0 +) + +replace ( + resolv => ../resolv_tailored +) diff --git a/jsexport/go.sum b/jsexport/go.sum new file mode 100644 index 0000000..e8d6223 --- /dev/null +++ b/jsexport/go.sum @@ -0,0 +1,2 @@ +github.com/gopherjs/gopherjs v1.18.0-beta1 h1:IbykhVEq4SAjwyBRuNHl0aOO6w6IqgL3RUdMhoBo4mY= +github.com/gopherjs/gopherjs v1.18.0-beta1/go.mod h1:6UY8PXRnu51MqjYCCY4toG0S5GeH5uVJ3qDxIsa+kqo= diff --git a/jsexport/main.go b/jsexport/main.go new file mode 100644 index 0000000..6f6cf68 --- /dev/null +++ b/jsexport/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "github.com/gopherjs/gopherjs/js" + . "jsexport/battle" + "resolv" +) + +func NewInputFrameDownsync(inputFrameId int32, inputList []uint64, confirmedList uint64) *js.Object { + return js.MakeFullWrapper(&InputFrameDownsync{ + InputFrameId: inputFrameId, + InputList: inputList, + ConfirmedList: confirmedList, + }) +} + +func NewRingBufferJs(n int32) *js.Object { + return js.MakeFullWrapper(NewRingBuffer(n)) +} + +func NewCollisionSpaceJs(spaceW, spaceH, minStepW, minStepH int) *js.Object { + return js.MakeWrapper(resolv.NewSpace(spaceW, spaceH, minStepW, minStepH)) +} + +func NewVec2DJs(x, y float64) *js.Object { + return js.MakeFullWrapper(&Vec2D{ + X: x, + Y: y, + }) +} + +func NewPolygon2DJs(anchor *Vec2D, points []*Vec2D) *js.Object { + return js.MakeFullWrapper(&Polygon2D{ + Anchor: anchor, + Points: points, + }) +} + +func NewBarrierJs(boundary *Polygon2D) *js.Object { + return js.MakeWrapper(&Barrier{ + Boundary: boundary, + }) +} + +func NewPlayerDownsyncJs(id, virtualGridX, virtualGridY, dirX, dirY, velX, velY, framesToRecover, speed, battleState, characterState, joinIndex, hp, maxHp int32, inAir bool, colliderRadius float64) *js.Object { + return js.MakeWrapper(&PlayerDownsync{ + Id: id, + VirtualGridX: virtualGridX, + VirtualGridY: virtualGridY, + DirX: dirX, + DirY: dirY, + VelX: velX, + VelY: velY, + FramesToRecover: framesToRecover, + Speed: speed, + BattleState: battleState, + JoinIndex: joinIndex, + ColliderRadius: colliderRadius, + Hp: hp, + MaxHp: maxHp, + CharacterState: characterState, + 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 { + return js.MakeWrapper(&MeleeBullet{ + Bullet: Bullet{ + BattleLocalId: battleLocalId, + StartupFrames: startupFrames, + ActiveFrames: activeFrames, + RecoveryFrames: recoveryFrames, + RecoveryFramesOnBlock: recoveryFramesOnBlock, + RecoveryFramesOnHit: recoveryFramesOnHit, + HitboxOffset: hitboxOffset, + HitStunFrames: hitStunFrames, + BlockStunFrames: blockStunFrames, + Pushback: pushback, + ReleaseTriggerType: releaseTriggerType, + Damage: damage, + + SelfMoveforwardX: selfMoveforwardX, + SelfMoveforwardY: selfMoveforwardY, + HitboxSizeX: hitboxSizeX, + HitboxSizeY: hitboxSizeY, + + OffenderJoinIndex: offenderJoinIndex, + OffenderPlayerId: offenderPlayerId, + }, + }) +} + +func NewRoomDownsyncFrameJs(id int32, playersArr []*PlayerDownsync, meleeBullets []*MeleeBullet) *js.Object { + // [WARNING] Avoid using "pb.RoomDownsyncFrame" here, in practive "MakeFullWrapper" doesn't expose the public fields for a "protobuf struct" as expected and requires helper functions like "GetCollisionSpaceObjsJs". + return js.MakeFullWrapper(&RoomDownsyncFrame{ + Id: id, + PlayersArr: playersArr, + MeleeBullets: meleeBullets, + }) +} + +func GetCollisionSpaceObjsJs(space *resolv.Space) []*js.Object { + // [WARNING] We couldn't just use the existing method "space.Objects()" to access them in JavaScript, there'd a stackoverflow error + objs := space.Objects() + ret := make([]*js.Object, 0, len(objs)) + for _, obj := range objs { + ret = append(ret, js.MakeFullWrapper(obj)) + } + return ret +} + +func GenerateRectColliderJs(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { + /* + [WARNING] It's important to note that we don't need "js.MakeFullWrapper" for a call sequence as follows. + ``` + var space = gopkgs.NewCollisionSpaceJs(2048, 2048, 8, 8); + var a = gopkgs.GenerateRectColliderJs(189, 497, 48, 48, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, snapIntoPlatformOverlap, spaceOffsetX, spaceOffsetY, "Player"); + space.Add(a); + ``` + The "space" variable doesn't need access to the field of "a" in JavaScript level to run "space.Add(...)" method, which is good. + + However, the full wrapper access here is used for updating "collider.X/collider.Y" at JavaScript runtime. + */ + return js.MakeFullWrapper(GenerateRectCollider(wx, wy, w, h, topPadding, bottomPadding, leftPadding, rightPadding, spaceOffsetX, spaceOffsetY, data, tag)) + +} + +func GenerateConvexPolygonColliderJs(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, data interface{}, tag string) *js.Object { + return js.MakeFullWrapper(GenerateConvexPolygonCollider(unalignedSrc, spaceOffsetX, spaceOffsetY, data, tag)) +} + +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 { + // 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)) +} + +func main() { + js.Global.Set("gopkgs", map[string]interface{}{ + "NewVec2DJs": NewVec2DJs, + "NewPolygon2DJs": NewPolygon2DJs, + "NewBarrierJs": NewBarrierJs, + "NewPlayerDownsyncJs": NewPlayerDownsyncJs, + "NewMeleeBulletJs": NewMeleeBulletJs, + "NewRoomDownsyncFrameJs": NewRoomDownsyncFrameJs, + "NewCollisionSpaceJs": NewCollisionSpaceJs, + "NewInputFrameDownsync": NewInputFrameDownsync, + "NewRingBufferJs": NewRingBufferJs, + "GenerateRectColliderJs": GenerateRectColliderJs, + "GenerateConvexPolygonColliderJs": GenerateConvexPolygonColliderJs, + "GetCollisionSpaceObjsJs": GetCollisionSpaceObjsJs, + "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs": ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs, + "WorldToPolygonColliderBLPos": WorldToPolygonColliderBLPos, // No need to wrap primitive return types + "PolygonColliderBLToWorldPos": PolygonColliderBLToWorldPos, + }) +} diff --git a/proto_gen_shortcut.sh b/proto_gen_shortcut.sh index 505cf68..dbceeb1 100755 --- a/proto_gen_shortcut.sh +++ b/proto_gen_shortcut.sh @@ -6,7 +6,7 @@ golang_basedir_1=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/dnmshared protoc -I=$golang_basedir_1/../frontend/assets/resources/pbfiles/ --go_out=. geometry.proto -echo "GOLANG part 1 done" +echo "GOLANG part 1 done" # [WARNING] The output of "part 1" is DEPRECATED, the codes are not using it anymore. # [WARNING] The following "room_downsync_frame.proto" is generated in another Go package than "geometry.proto", but the generated Go codes are also required to work with imports correctly! golang_basedir_2=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/battle_srv diff --git a/resolv_tailored/.gitignore b/resolv_tailored/.gitignore new file mode 100644 index 0000000..b75c52a --- /dev/null +++ b/resolv_tailored/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +Game + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +.vscode/launch.json diff --git a/resolv_tailored/LICENSE b/resolv_tailored/LICENSE new file mode 100644 index 0000000..09c9cd6 --- /dev/null +++ b/resolv_tailored/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2021 SolarLune + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/resolv_tailored/asm_axpyunitaryto_amd64.s b/resolv_tailored/asm_axpyunitaryto_amd64.s new file mode 100644 index 0000000..f6eaade --- /dev/null +++ b/resolv_tailored/asm_axpyunitaryto_amd64.s @@ -0,0 +1,164 @@ +// This file contains code from the gonum repository: +// https://github.com/gonum/gonum/blob/master/internal/asm/f64/scalunitaryto_amd64.s +// it is distributed under the 3-Clause BSD license: +// +// Copyright ©2013 The Gonum Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Gonum project nor the names of its authors and +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Some of the loop unrolling code is copied from: +// http://golang.org/src/math/big/arith_amd64.s +// which is distributed under these terms: +// +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build !noasm + +#include "textflag.h" + +#define X_PTR SI +#define Y_PTR DX +#define DST_PTR DI +#define IDX AX +#define LEN CX +#define TAIL BX +#define ALPHA X0 +#define ALPHA_2 X1 + +// func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64) +TEXT ·axpyUnitaryTo(SB), NOSPLIT, $0 + MOVQ dst_base+0(FP), DST_PTR // DST_PTR := &dst + MOVQ x_base+32(FP), X_PTR // X_PTR := &x + MOVQ y_base+56(FP), Y_PTR // Y_PTR := &y + MOVQ x_len+40(FP), LEN // LEN = min( len(x), len(y), len(dst) ) + CMPQ y_len+64(FP), LEN + CMOVQLE y_len+64(FP), LEN + CMPQ dst_len+8(FP), LEN + CMOVQLE dst_len+8(FP), LEN + + CMPQ LEN, $0 + JE end // if LEN == 0 { return } + + XORQ IDX, IDX // IDX = 0 + MOVSD alpha+24(FP), ALPHA + SHUFPD $0, ALPHA, ALPHA // ALPHA := { alpha, alpha } + MOVQ Y_PTR, TAIL // Check memory alignment + ANDQ $15, TAIL // TAIL = &y % 16 + JZ no_trim // if TAIL == 0 { goto no_trim } + + // Align on 16-byte boundary + MOVSD (X_PTR), X2 // X2 := x[0] + MULSD ALPHA, X2 // X2 *= a + ADDSD (Y_PTR), X2 // X2 += y[0] + MOVSD X2, (DST_PTR) // y[0] = X2 + INCQ IDX // i++ + DECQ LEN // LEN-- + JZ end // if LEN == 0 { return } + +no_trim: + MOVQ LEN, TAIL + ANDQ $7, TAIL // TAIL := n % 8 + SHRQ $3, LEN // LEN = floor( n / 8 ) + JZ tail_start // if LEN == 0 { goto tail_start } + + MOVUPS ALPHA, ALPHA_2 // ALPHA_2 := ALPHA for pipelining + +loop: // do { + // y[i] += alpha * x[i] unrolled 8x. + MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i] + MOVUPS 16(X_PTR)(IDX*8), X3 + MOVUPS 32(X_PTR)(IDX*8), X4 + MOVUPS 48(X_PTR)(IDX*8), X5 + + MULPD ALPHA, X2 // X_i *= alpha + MULPD ALPHA_2, X3 + MULPD ALPHA, X4 + MULPD ALPHA_2, X5 + + ADDPD (Y_PTR)(IDX*8), X2 // X_i += y[i] + ADDPD 16(Y_PTR)(IDX*8), X3 + ADDPD 32(Y_PTR)(IDX*8), X4 + ADDPD 48(Y_PTR)(IDX*8), X5 + + MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X_i + MOVUPS X3, 16(DST_PTR)(IDX*8) + MOVUPS X4, 32(DST_PTR)(IDX*8) + MOVUPS X5, 48(DST_PTR)(IDX*8) + + ADDQ $8, IDX // i += 8 + DECQ LEN + JNZ loop // } while --LEN > 0 + CMPQ TAIL, $0 // if TAIL == 0 { return } + JE end + +tail_start: // Reset loop registers + MOVQ TAIL, LEN // Loop counter: LEN = TAIL + SHRQ $1, LEN // LEN = floor( TAIL / 2 ) + JZ tail_one // if LEN == 0 { goto tail } + +tail_two: // do { + MOVUPS (X_PTR)(IDX*8), X2 // X2 = x[i] + MULPD ALPHA, X2 // X2 *= alpha + ADDPD (Y_PTR)(IDX*8), X2 // X2 += y[i] + MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X2 + ADDQ $2, IDX // i += 2 + DECQ LEN + JNZ tail_two // } while --LEN > 0 + + ANDQ $1, TAIL + JZ end // if TAIL == 0 { goto end } + +tail_one: + MOVSD (X_PTR)(IDX*8), X2 // X2 = x[i] + MULSD ALPHA, X2 // X2 *= a + ADDSD (Y_PTR)(IDX*8), X2 // X2 += y[i] + MOVSD X2, (DST_PTR)(IDX*8) // y[i] = X2 + +end: + RET diff --git a/resolv_tailored/asm_scalunitaryto_amd64.s b/resolv_tailored/asm_scalunitaryto_amd64.s new file mode 100644 index 0000000..e7cbeb1 --- /dev/null +++ b/resolv_tailored/asm_scalunitaryto_amd64.s @@ -0,0 +1,137 @@ +// This file contains code from the gonum repository: +// https://github.com/gonum/gonum/blob/master/internal/asm/f64/axpyunitaryto_amd64.s +// it is distributed under the 3-Clause BSD license: +// +// Copyright ©2013 The Gonum Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Gonum project nor the names of its authors and +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Some of the loop unrolling code is copied from: +// http://golang.org/src/math/big/arith_amd64.s +// which is distributed under these terms: +// +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build !noasm + +#include "textflag.h" + +#define MOVDDUP_ALPHA LONG $0x44120FF2; WORD $0x2024 // @ MOVDDUP 32(SP), X0 /*XMM0, 32[RSP]*/ + +#define X_PTR SI +#define DST_PTR DI +#define IDX AX +#define LEN CX +#define TAIL BX +#define ALPHA X0 +#define ALPHA_2 X1 + +// func scalUnitaryTo(dst []float64, alpha float64, x []float64) +// This function assumes len(dst) >= len(x). +TEXT ·scalUnitaryTo(SB), NOSPLIT, $0 + MOVQ x_base+32(FP), X_PTR // X_PTR = &x + MOVQ dst_base+0(FP), DST_PTR // DST_PTR = &dst + MOVDDUP_ALPHA // ALPHA = { alpha, alpha } + MOVQ x_len+40(FP), LEN // LEN = len(x) + CMPQ LEN, $0 + JE end // if LEN == 0 { return } + + XORQ IDX, IDX // IDX = 0 + MOVQ LEN, TAIL + ANDQ $7, TAIL // TAIL = LEN % 8 + SHRQ $3, LEN // LEN = floor( LEN / 8 ) + JZ tail_start // if LEN == 0 { goto tail_start } + + MOVUPS ALPHA, ALPHA_2 // ALPHA_2 = ALPHA for pipelining + +loop: // do { // dst[i] = alpha * x[i] unrolled 8x. + MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i] + MOVUPS 16(X_PTR)(IDX*8), X3 + MOVUPS 32(X_PTR)(IDX*8), X4 + MOVUPS 48(X_PTR)(IDX*8), X5 + + MULPD ALPHA, X2 // X_i *= ALPHA + MULPD ALPHA_2, X3 + MULPD ALPHA, X4 + MULPD ALPHA_2, X5 + + MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i + MOVUPS X3, 16(DST_PTR)(IDX*8) + MOVUPS X4, 32(DST_PTR)(IDX*8) + MOVUPS X5, 48(DST_PTR)(IDX*8) + + ADDQ $8, IDX // i += 8 + DECQ LEN + JNZ loop // while --LEN > 0 + CMPQ TAIL, $0 + JE end // if TAIL == 0 { return } + +tail_start: // Reset loop counters + MOVQ TAIL, LEN // Loop counter: LEN = TAIL + SHRQ $1, LEN // LEN = floor( TAIL / 2 ) + JZ tail_one // if LEN == 0 { goto tail_one } + +tail_two: // do { + MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i] + MULPD ALPHA, X2 // X_i *= ALPHA + MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i + ADDQ $2, IDX // i += 2 + DECQ LEN + JNZ tail_two // while --LEN > 0 + + ANDQ $1, TAIL + JZ end // if TAIL == 0 { return } + +tail_one: + MOVSD (X_PTR)(IDX*8), X2 // X_i = x[i] + MULSD ALPHA, X2 // X_i *= ALPHA + MOVSD X2, (DST_PTR)(IDX*8) // dst[i] = X_i + +end: + RET diff --git a/resolv_tailored/asm_stubs_amd64.go b/resolv_tailored/asm_stubs_amd64.go new file mode 100644 index 0000000..9503c26 --- /dev/null +++ b/resolv_tailored/asm_stubs_amd64.go @@ -0,0 +1,9 @@ +//go:build !noasm +// +build !noasm + +package resolv + +// functions from the gonum package that optimizes arithmetic +// operations on lists of float64 values +func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64) +func scalUnitaryTo(dst []float64, alpha float64, x []float64) diff --git a/resolv_tailored/cell.go b/resolv_tailored/cell.go new file mode 100644 index 0000000..fc4b466 --- /dev/null +++ b/resolv_tailored/cell.go @@ -0,0 +1,63 @@ +package resolv + +// Cell is used to contain and organize Object information. +type Cell struct { + X, Y int // The X and Y position of the cell in the Space - note that this is in Grid position, not World position. + Objects []*Object // The Objects that a Cell contains. +} + +// newCell creates a new cell at the specified X and Y position. Should not be used directly. +func newCell(x, y int) *Cell { + return &Cell{ + X: x, + Y: y, + Objects: []*Object{}, + } +} + +// register registers an object with a Cell. Should not be used directly. +func (cell *Cell) register(obj *Object) { + if !cell.Contains(obj) { + cell.Objects = append(cell.Objects, obj) + } +} + +// unregister unregisters an object from a Cell. Should not be used directly. +func (cell *Cell) unregister(obj *Object) { + + for i, o := range cell.Objects { + + if o == obj { + cell.Objects[i] = cell.Objects[len(cell.Objects)-1] + cell.Objects = cell.Objects[:len(cell.Objects)-1] + break + } + + } + +} + +// Contains returns whether a Cell contains the specified Object at its position. +func (cell *Cell) Contains(obj *Object) bool { + for _, o := range cell.Objects { + if o == obj { + return true + } + } + return false +} + +// ContainsTags returns whether a Cell contains an Object that has the specified tag at its position. +func (cell *Cell) ContainsTags(tags ...string) bool { + for _, o := range cell.Objects { + if o.HasTags(tags...) { + return true + } + } + return false +} + +// Occupied returns whether a Cell contains any Objects at all. +func (cell *Cell) Occupied() bool { + return len(cell.Objects) > 0 +} diff --git a/resolv_tailored/collision.go b/resolv_tailored/collision.go new file mode 100644 index 0000000..4d99d12 --- /dev/null +++ b/resolv_tailored/collision.go @@ -0,0 +1,156 @@ +package resolv + +// Collision contains the results of an Object.Check() call, and represents a collision between an Object and cells that contain other Objects. +// The Objects array indicate the Objects collided with. +type Collision struct { + checkingObject *Object // The checking object + dx, dy float64 // The delta the checking object was moving on that caused this collision + Objects []*Object // Slice of objects that were collided with; sorted according to distance to calling Object. + Cells []*Cell // Slice of cells that were collided with; sorted according to distance to calling Object. +} + +func NewCollision() *Collision { + return &Collision{ + Objects: []*Object{}, + } +} + +// HasTags returns whether any objects within the Collision have all of the specified tags. This slice does not contain the Object that called Check(). +func (cc *Collision) HasTags(tags ...string) bool { + + for _, o := range cc.Objects { + + if o == cc.checkingObject { + continue + } + if o.HasTags(tags...) { + return true + } + + } + + return false +} + +// ObjectsByTags returns a slice of Objects from the cells reported by a Collision object by searching for Objects with a specific set of tags. +// This slice does not contain the Object that called Check(). +func (cc *Collision) ObjectsByTags(tags ...string) []*Object { + + objects := []*Object{} + + for _, o := range cc.Objects { + + if o == cc.checkingObject { + continue + } + if o.HasTags(tags...) { + objects = append(objects, o) + } + + } + + return objects + +} + +// ContactWithObject returns the delta to move to come into contact with the specified Object. +func (cc *Collision) ContactWithObject(object *Object) Vector { + + delta := Vector{0, 0} + + if cc.dx < 0 { + delta[0] = object.X + object.W - cc.checkingObject.X + } else if cc.dx > 0 { + delta[0] = object.X - cc.checkingObject.W - cc.checkingObject.X + } + + if cc.dy < 0 { + delta[1] = object.Y + object.H - cc.checkingObject.Y + } else if cc.dy > 0 { + delta[1] = object.Y - cc.checkingObject.H - cc.checkingObject.Y + } + + return delta + +} + +// ContactWithCell returns the delta to move to come into contact with the specified Cell. +func (cc *Collision) ContactWithCell(cell *Cell) Vector { + + delta := Vector{0, 0} + + cx := float64(cell.X * cc.checkingObject.Space.CellWidth) + cy := float64(cell.Y * cc.checkingObject.Space.CellHeight) + + if cc.dx < 0 { + delta[0] = cx + float64(cc.checkingObject.Space.CellWidth) - cc.checkingObject.X + } else if cc.dx > 0 { + delta[0] = cx - cc.checkingObject.W - cc.checkingObject.X + } + + if cc.dy < 0 { + delta[1] = cy + float64(cc.checkingObject.Space.CellHeight) - cc.checkingObject.Y + } else if cc.dy > 0 { + delta[1] = cy - cc.checkingObject.H - cc.checkingObject.Y + } + + return delta + +} + +// SlideAgainstCell returns how much distance the calling Object can slide to avoid a collision with the targetObject. This only works on vertical and horizontal axes (x and y directly), +// primarily for platformers / top-down games. avoidTags is a sequence of tags (as strings) to indicate when sliding is valid (i.e. if a Cell contains an Object that has the tag given in +// the avoidTags slice, then sliding CANNOT happen). If sliding is not able to be done for whatever reason, SlideAgainstCell returns nil. +func (cc *Collision) SlideAgainstCell(cell *Cell, avoidTags ...string) Vector { + + sp := cc.checkingObject.Space + + collidingCell := cc.Cells[0] + ccX, ccY := sp.SpaceToWorld(collidingCell.X, collidingCell.Y) + hX := float64(sp.CellWidth) / 2.0 + hY := float64(sp.CellHeight) / 2.0 + + ccX += hX + ccY += hY + + oX, oY := cc.checkingObject.Center() + + diffX := oX - ccX + diffY := oY - ccY + + left := sp.Cell(collidingCell.X-1, collidingCell.Y) + right := sp.Cell(collidingCell.X+1, collidingCell.Y) + up := sp.Cell(collidingCell.X, collidingCell.Y-1) + down := sp.Cell(collidingCell.X, collidingCell.Y+1) + + slide := Vector{0, 0} + + // Moving vertically + if cc.dy != 0 { + + if diffX > 0 && (right == nil || !right.ContainsTags(avoidTags...)) { + // Slide right + slide[0] = ccX + hX - cc.checkingObject.X + } else if diffX < 0 && (left == nil || !left.ContainsTags(avoidTags...)) { + // Slide left + slide[0] = ccX - hX - (cc.checkingObject.X + cc.checkingObject.W) + } else { + return nil + } + } + + if cc.dx != 0 { + if diffY > 0 && (down == nil || !down.ContainsTags(avoidTags...)) { + // Slide down + slide[1] = ccY + hY - cc.checkingObject.Y + } else if diffY < 0 && (up == nil || !up.ContainsTags(avoidTags...)) { + // Slide up + slide[1] = ccY - hY - (cc.checkingObject.Y + cc.checkingObject.H) + } else { + return nil + } + } + + return slide + +} diff --git a/resolv_tailored/go.mod b/resolv_tailored/go.mod new file mode 100644 index 0000000..454ed1d --- /dev/null +++ b/resolv_tailored/go.mod @@ -0,0 +1,3 @@ +module resolv + +go 1.18 diff --git a/resolv_tailored/go.sum b/resolv_tailored/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/resolv_tailored/gonum.go b/resolv_tailored/gonum.go new file mode 100644 index 0000000..7d30026 --- /dev/null +++ b/resolv_tailored/gonum.go @@ -0,0 +1,24 @@ +//go:build !amd64 || noasm +// +build !amd64 noasm + +package resolv + +// This function is from the gonum repository: +// https://github.com/gonum/gonum/blob/c3867503e73e5c3fee7ab93e3c2c562eb2be8178/internal/asm/f64/axpy.go#L23 +func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64) { + dim := len(y) + for i, v := range x { + if i == dim { + return + } + dst[i] = alpha*v + y[i] + } +} + +// This function is from the gonum repository: +// https://github.com/gonum/gonum/blob/c3867503e73e5c3fee7ab93e3c2c562eb2be8178/internal/asm/f64/scal.go#L23 +func scalUnitaryTo(dst []float64, alpha float64, x []float64) { + for i := range x { + dst[i] *= alpha + } +} diff --git a/resolv_tailored/object.go b/resolv_tailored/object.go new file mode 100644 index 0000000..e990d44 --- /dev/null +++ b/resolv_tailored/object.go @@ -0,0 +1,328 @@ +package resolv + +import ( + "math" + //"sort" +) + +// Object represents an object that can be spread across one or more Cells in a Space. An Object is essentially an AABB (Axis-Aligned Bounding Box) Rectangle. +type Object struct { + Shape Shape // A shape for more specific collision-checking. + Space *Space // Reference to the Space the Object exists within + X, Y, W, H float64 // Position and size of the Object in the Space + TouchingCells []*Cell // An array of Cells the Object is touching + Data interface{} // A pointer to a user-definable object + ignoreList map[*Object]bool // Set of Objects to ignore when checking for collisions + tags []string // A list of tags the Object has +} + +// NewObject returns a new Object of the specified position and size. +func NewObject(x, y, w, h float64, tags ...string) *Object { + o := &Object{ + X: x, + Y: y, + W: w, + H: h, + tags: []string{}, + ignoreList: map[*Object]bool{}, + } + + if len(tags) > 0 { + o.AddTags(tags...) + } + + return o +} + +// Clone clones the Object with its properties into another Object. It also clones the Object's Shape (if it has one). +func (obj *Object) Clone() *Object { + newObj := NewObject(obj.X, obj.Y, obj.W, obj.H, obj.Tags()...) + newObj.Data = obj.Data + if obj.Shape != nil { + newObj.SetShape(obj.Shape.Clone()) + } + for k := range obj.ignoreList { + newObj.AddToIgnoreList(k) + } + return newObj +} + +// Update updates the object's association to the Cells in the Space. This should be called whenever an Object is moved. +// This is automatically called once when creating the Object, so you don't have to call it for static objects. +func (obj *Object) Update() { + + if obj.Space != nil { + + // Object.Space.Remove() sets the removed object's Space to nil, indicating it's been removed. Because we're updating + // the Object (which is essentially removing it from its previous Cells / position and re-adding it to the new Cells / + // position), we store the original Space to re-set it. + + space := obj.Space + + obj.Space.Remove(obj) + + obj.Space = space + + cx, cy, ex, ey := obj.BoundsToSpace(0, 0) + + for y := cy; y <= ey; y++ { + + for x := cx; x <= ex; x++ { + + c := obj.Space.Cell(x, y) + + if c != nil { + c.register(obj) + obj.TouchingCells = append(obj.TouchingCells, c) + } + + } + + } + + } + + if obj.Shape != nil { + obj.Shape.SetPosition(obj.X, obj.Y) + } + +} + +// AddTags adds tags to the Object. +func (obj *Object) AddTags(tags ...string) { + obj.tags = append(obj.tags, tags...) +} + +// RemoveTags removes tags from the Object. +func (obj *Object) RemoveTags(tags ...string) { + + for _, tag := range tags { + + for i, t := range obj.tags { + + if t == tag { + obj.tags = append(obj.tags[:i], obj.tags[i+1:]...) + break + } + + } + + } + +} + +// HasTags indicates if an Object has any of the tags indicated. +func (obj *Object) HasTags(tags ...string) bool { + + for _, tag := range tags { + + for _, t := range obj.tags { + + if t == tag { + return true + } + + } + + } + + return false + +} + +// Tags returns the tags an Object has. +func (obj *Object) Tags() []string { + return append([]string{}, obj.tags...) +} + +// SetShape sets the Shape on the Object, in case you need to use precise per-Shape intersection detection. SetShape calls Object.Update() as well, so that it's able to +// update the Shape's position to match its Object as necessary. (If you don't use this, the Shape's position might not match the Object's, depending on if you set the Shape +// after you added the Object to a Space and if you don't call Object.Update() yourself afterwards.) +func (obj *Object) SetShape(shape Shape) { + if obj.Shape != shape { + obj.Shape = shape + obj.Update() + } +} + +// BoundsToSpace returns the Space coordinates of the shape (x, y, w, and h), given its world position and size, and a supposed movement of dx and dy. +func (obj *Object) BoundsToSpace(dx, dy float64) (int, int, int, int) { + cx, cy := obj.Space.WorldToSpace(obj.X+dx, obj.Y+dy) + ex, ey := obj.Space.WorldToSpace(obj.X+obj.W+dx-1, obj.Y+obj.H+dy-1) + return cx, cy, ex, ey +} + +// SharesCells returns whether the Object occupies a cell shared by the specified other Object. +func (obj *Object) SharesCells(other *Object) bool { + for _, cell := range obj.TouchingCells { + if cell.Contains(other) { + return true + } + } + return false +} + +// SharesCellsTags returns if the Cells the Object occupies have an object with the specified tags. +func (obj *Object) SharesCellsTags(tags ...string) bool { + for _, cell := range obj.TouchingCells { + if cell.ContainsTags(tags...) { + return true + } + } + return false +} + +// Center returns the center position of the Object. +func (obj *Object) Center() (float64, float64) { + return obj.X + (obj.W / 2.0), obj.Y + (obj.H / 2.0) +} + +// SetCenter sets the Object such that its center is at the X and Y position given. +func (obj *Object) SetCenter(x, y float64) { + obj.X = x - (obj.W / 2) + obj.Y = y - (obj.H / 2) +} + +// CellPosition returns the cellular position of the Object's center in the Space. +func (obj *Object) CellPosition() (int, int) { + return obj.Space.WorldToSpace(obj.Center()) +} + +// SetRight sets the X position of the Object so the right edge is at the X position given. +func (obj *Object) SetRight(x float64) { + obj.X = x - obj.W +} + +// SetBottom sets the Y position of the Object so that the bottom edge is at the Y position given. +func (obj *Object) SetBottom(y float64) { + obj.Y = y - obj.H +} + +// Bottom returns the bottom Y coordinate of the Object (i.e. object.Y + object.H). +func (obj *Object) Bottom() float64 { + return obj.Y + obj.H +} + +// Right returns the right X coordinate of the Object (i.e. object.X + object.W). +func (obj *Object) Right() float64 { + return obj.X + obj.W +} + +func (obj *Object) SetBounds(topLeft, bottomRight Vector) { + obj.X = topLeft[0] + obj.Y = topLeft[1] + obj.W = bottomRight[0] - obj.X + obj.H = bottomRight[1] - obj.Y +} + +// Check checks the space around the object using the designated delta movement (dx and dy). This is done by querying the containing Space's Cells +// so that it can see if moving it would coincide with a cell that houses another Object (filtered using the given selection of tag strings). If so, +// Check returns a Collision. If no objects are found or the Object does not exist within a Space, this function returns nil. +func (obj *Object) Check(dx, dy float64, tags ...string) *Collision { + + if obj.Space == nil { + return nil + } + + cc := NewCollision() + cc.checkingObject = obj + + if dx < 0 { + dx = math.Min(dx, -1) + } else if dx > 0 { + dx = math.Max(dx, 1) + } + + if dy < 0 { + dy = math.Min(dy, -1) + } else if dy > 0 { + dy = math.Max(dy, 1) + } + + cc.dx = dx + cc.dy = dy + + cx, cy, ex, ey := obj.BoundsToSpace(dx, dy) + + objectsAdded := map[*Object]bool{} + cellsAdded := map[*Cell]bool{} + + for y := cy; y <= ey; y++ { + + for x := cx; x <= ex; x++ { + + if c := obj.Space.Cell(x, y); c != nil { + + for _, o := range c.Objects { + + // We only want cells that have objects other than the checking object, or that aren't on the ignore list. + if ignored := obj.ignoreList[o]; o == obj || ignored { + continue + } + + if _, added := objectsAdded[o]; (len(tags) == 0 || o.HasTags(tags...)) && !added { + + cc.Objects = append(cc.Objects, o) + objectsAdded[o] = true + if _, added := cellsAdded[c]; !added { + cc.Cells = append(cc.Cells, c) + cellsAdded[c] = true + } + continue + + } + + } + + } + + } + + } + + if len(cc.Objects) == 0 { + return nil + } + + /* + // In my use case, order of objects within a collision instance is not needed, and this also favors both runtime performance & size reduction of `jsexport.js`. + + ox, oy := cc.checkingObject.Center() + oc := Vector{ox, oy} + sort.Slice(cc.Objects, func(i, j int) bool { + + ix, iy := cc.Objects[i].Center() + jx, jy := cc.Objects[j].Center() + return Vector{ix, iy}.Sub(oc).Magnitude2() < Vector{jx, jy}.Sub(oc).Magnitude2() + + }) + + cw := cc.checkingObject.Space.CellWidth + ch := cc.checkingObject.Space.CellHeight + + sort.Slice(cc.Cells, func(i, j int) bool { + + return Vector{float64(cc.Cells[i].X*cw + (cw / 2)), float64(cc.Cells[i].Y*ch + (ch / 2))}.Sub(oc).Magnitude2() < + Vector{float64(cc.Cells[j].X*cw + (cw / 2)), float64(cc.Cells[j].Y*ch + (ch / 2))}.Sub(oc).Magnitude2() + + }) + */ + + return cc + +} + +// Overlaps returns if an Object overlaps another Object. +func (obj *Object) Overlaps(other *Object) bool { + return other.X <= obj.X+obj.W && other.X+other.W >= obj.X && other.Y <= obj.Y+obj.H && other.Y+other.H >= obj.Y +} + +// AddToIgnoreList adds the specified Object to the Object's internal collision ignoral list. Cells that contain the specified Object will not be counted when calling Check(). +func (obj *Object) AddToIgnoreList(ignoreObj *Object) { + obj.ignoreList[ignoreObj] = true +} + +// RemoveFromIgnoreList removes the specified Object from the Object's internal collision ignoral list. Objects removed from this list will once again be counted for Check(). +func (obj *Object) RemoveFromIgnoreList(ignoreObj *Object) { + delete(obj.ignoreList, ignoreObj) +} diff --git a/resolv_tailored/shape.go b/resolv_tailored/shape.go new file mode 100644 index 0000000..975016f --- /dev/null +++ b/resolv_tailored/shape.go @@ -0,0 +1,754 @@ +package resolv + +import ( + "math" +) + +type Shape interface { + // Intersection tests to see if a Shape intersects with the other given Shape. dx and dy are delta movement variables indicating + // movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it + // were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding + // the intersection. + Intersection(dx, dy float64, other Shape) *ContactSet + // Bounds returns the top-left and bottom-right points of the Shape. + Bounds() (Vector, Vector) + // Position returns the X and Y position of the Shape. + Position() (float64, float64) + // SetPosition allows you to place a Shape at another location. + SetPosition(x, y float64) + // Clone duplicates the Shape. + Clone() Shape +} + +// A Line is a helper shape used to determine if two ConvexPolygon lines intersect; you can't create a Line to use as a Shape. +// Instead, you can create a ConvexPolygon, specify two points, and set its Closed value to false. +type Line struct { + Start, End Vector +} + +func NewLine(x, y, x2, y2 float64) *Line { + return &Line{ + Start: Vector{x, y}, + End: Vector{x2, y2}, + } +} + +func (line *Line) Project(axis Vector) Vector { + return line.Vector().Scale(axis.Dot(line.Start.Sub(line.End))) +} + +func (line *Line) Normal() Vector { + v := line.Vector() + return Vector{v[1], -v[0]}.Unit() +} + +func (line *Line) Vector() Vector { + return line.End.Clone().Sub(line.Start).Unit() +} + +// IntersectionPointsLine returns the intersection point of a Line with another Line as a Vector. If no intersection is found, it will return nil. +func (line *Line) IntersectionPointsLine(other *Line) Vector { + + det := (line.End[0]-line.Start[0])*(other.End[1]-other.Start[1]) - (other.End[0]-other.Start[0])*(line.End[1]-line.Start[1]) + + if det != 0 { + + // MAGIC MATH; the extra + 1 here makes it so that corner cases (literally, lines going through corners) works. + + // lambda := (float32(((line.Y-b.Y)*(b.X2-b.X))-((line.X-b.X)*(b.Y2-b.Y))) + 1) / float32(det) + lambda := (((line.Start[1] - other.Start[1]) * (other.End[0] - other.Start[0])) - ((line.Start[0] - other.Start[0]) * (other.End[1] - other.Start[1])) + 1) / det + + // gamma := (float32(((line.Y-b.Y)*(line.X2-line.X))-((line.X-b.X)*(line.Y2-line.Y))) + 1) / float32(det) + gamma := (((line.Start[1] - other.Start[1]) * (line.End[0] - line.Start[0])) - ((line.Start[0] - other.Start[0]) * (line.End[1] - line.Start[1])) + 1) / det + + if (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1) { + + // Delta + dx := line.End[0] - line.Start[0] + dy := line.End[1] - line.Start[1] + + // dx, dy := line.GetDelta() + + return Vector{line.Start[0] + (lambda * dx), line.Start[1] + (lambda * dy)} + } + + } + + return nil + +} + +// IntersectionPointsCircle returns a slice of Vectors, each indicating the intersection point. If no intersection is found, it will return an empty slice. +func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector { + + points := []Vector{} + + cp := Vector{circle.X, circle.Y} + lStart := line.Start.Sub(cp) + lEnd := line.End.Sub(cp) + diff := lEnd.Sub(lStart) + + a := diff[0]*diff[0] + diff[1]*diff[1] + b := 2 * ((diff[0] * lStart[0]) + (diff[1] * lStart[1])) + c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.Radius * circle.Radius) + + det := b*b - (4 * a * c) + + if det < 0 { + // Do nothing, no intersections + } else if det == 0 { + + t := -b / (2 * a) + + if t >= 0 && t <= 1 { + points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]}) + } + + } else { + + t := (-b + math.Sqrt(det)) / (2 * a) + + // We have to ensure t is between 0 and 1; otherwise, the collision points are on the circle as though the lines were infinite in length. + if t >= 0 && t <= 1 { + points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]}) + } + t = (-b - math.Sqrt(det)) / (2 * a) + if t >= 0 && t <= 1 { + points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]}) + } + + } + + return points + +} + +type ConvexPolygon struct { + Points []Vector + X, Y float64 + Closed bool +} + +// NewConvexPolygon creates a new convex polygon from the provided set of X and Y positions of 2D points (or vertices). Should generally be ordered clockwise, +// from X and Y of the first, to X and Y of the last. For example: NewConvexPolygon(0, 0, 10, 0, 10, 10, 0, 10) would create a 10x10 convex +// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}. +func NewConvexPolygon(points ...float64) *ConvexPolygon { + + // if len(points)/2 < 2 { + // return nil + // } + + cp := &ConvexPolygon{Points: []Vector{}, Closed: true} + + cp.AddPoints(points...) + + return cp +} + +func (cp *ConvexPolygon) Clone() Shape { + + points := []Vector{} + + for _, point := range cp.Points { + points = append(points, point.Clone()) + } + + newPoly := NewConvexPolygon() + newPoly.X = cp.X + newPoly.Y = cp.Y + newPoly.AddPointsVec(points...) + newPoly.Closed = cp.Closed + return newPoly +} + +// AddPointsVec allows you to add points to the ConvexPolygon with a slice of Vectors, each indicating a point / vertex. +func (cp *ConvexPolygon) AddPointsVec(points ...Vector) { + cp.Points = append(cp.Points, points...) +} + +// AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for +// a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}). +func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) { + for v := 0; v < len(vertexPositions); v += 2 { + cp.Points = append(cp.Points, Vector{vertexPositions[v], vertexPositions[v+1]}) + } +} + +// Lines returns a slice of transformed Lines composing the ConvexPolygon. +func (cp *ConvexPolygon) Lines() []*Line { + + lines := []*Line{} + + vertices := cp.Transformed() + + for i := 0; i < len(vertices); i++ { + + start, end := vertices[i], vertices[0] + + if i < len(vertices)-1 { + end = vertices[i+1] + } else if !cp.Closed { + break + } + + line := NewLine(start[0], start[1], end[0], end[1]) + + lines = append(lines, line) + + } + + return lines + +} + +// Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position. +func (cp *ConvexPolygon) Transformed() []Vector { + transformed := []Vector{} + for _, point := range cp.Points { + transformed = append(transformed, Vector{point[0] + cp.X, point[1] + cp.Y}) + } + return transformed +} + +// Bounds returns two Vectors, comprising the top-left and bottom-right positions of the bounds of the +// ConvexPolygon, post-transformation. +func (cp *ConvexPolygon) Bounds() (Vector, Vector) { + + transformed := cp.Transformed() + + topLeft := Vector{transformed[0][0], transformed[0][1]} + bottomRight := topLeft.Clone() + + for i := 0; i < len(transformed); i++ { + + point := transformed[i] + + if point[0] < topLeft[0] { + topLeft[0] = point[0] + } else if point[0] > bottomRight[0] { + bottomRight[0] = point[0] + } + + if point[1] < topLeft[1] { + topLeft[1] = point[1] + } else if point[1] > bottomRight[1] { + bottomRight[1] = point[1] + } + + } + return topLeft, bottomRight +} + +// Position returns the position of the ConvexPolygon. +func (cp *ConvexPolygon) Position() (float64, float64) { + return cp.X, cp.Y +} + +// SetPosition sets the position of the ConvexPolygon. The offset of the vertices compared to the X and Y position is relative to however +// you initially defined the polygon and added the vertices. +func (cp *ConvexPolygon) SetPosition(x, y float64) { + cp.X = x + cp.Y = y +} + +// SetPositionVec allows you to set the position of the ConvexPolygon using a Vector. The offset of the vertices compared to the X and Y +// position is relative to however you initially defined the polygon and added the vertices. +func (cp *ConvexPolygon) SetPositionVec(vec Vector) { + cp.X = vec.X() + cp.Y = vec.Y() +} + +// Move translates the ConvexPolygon by the designated X and Y values. +func (cp *ConvexPolygon) Move(x, y float64) { + cp.X += x + cp.Y += y +} + +// MoveVec translates the ConvexPolygon by the designated Vector. +func (cp *ConvexPolygon) MoveVec(vec Vector) { + cp.X += vec.X() + cp.Y += vec.Y() +} + +// Center returns the transformed Center of the ConvexPolygon. +func (cp *ConvexPolygon) Center() Vector { + + pos := Vector{0, 0} + + for _, v := range cp.Transformed() { + pos.Add(v) + } + + pos[0] /= float64(len(cp.Transformed())) + pos[1] /= float64(len(cp.Transformed())) + + return pos + +} + +// Project projects (i.e. flattens) the ConvexPolygon onto the provided axis. +func (cp *ConvexPolygon) Project(axis Vector) Projection { + axis = axis.Unit() + vertices := cp.Transformed() + min := axis.Dot(Vector{vertices[0][0], vertices[0][1]}) + max := min + for i := 1; i < len(vertices); i++ { + p := axis.Dot(Vector{vertices[i][0], vertices[i][1]}) + if p < min { + min = p + } else if p > max { + max = p + } + } + return Projection{min, max} +} + +// SATAxes returns the axes of the ConvexPolygon for SAT intersection testing. +func (cp *ConvexPolygon) SATAxes() []Vector { + + axes := []Vector{} + for _, line := range cp.Lines() { + axes = append(axes, line.Normal()) + } + return axes + +} + +// PointInside returns if a Point (a Vector) is inside the ConvexPolygon. +func (polygon *ConvexPolygon) PointInside(point Vector) bool { + + pointLine := NewLine(point[0], point[1], point[0]+999999999999, point[1]) + + contactCount := 0 + + for _, line := range polygon.Lines() { + + if line.IntersectionPointsLine(pointLine) != nil { + contactCount++ + } + + } + + return contactCount == 1 +} + +type ContactSet struct { + Points []Vector // Slice of Points indicating contact between the two Shapes. + MTV Vector // Minimum Translation Vector; this is the vector to move a Shape on to move it outside of its contacting Shape. + Center Vector // Center of the Contact set; this is the average of all Points contained within the Contact Set. +} + +func NewContactSet() *ContactSet { + return &ContactSet{ + Points: []Vector{}, + MTV: Vector{0, 0}, + Center: Vector{0, 0}, + } +} + +// LeftmostPoint returns the left-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil. +func (cs *ContactSet) LeftmostPoint() Vector { + + var left Vector + + for _, point := range cs.Points { + + if left == nil || point[0] < left[0] { + left = point + } + + } + + return left + +} + +// RightmostPoint returns the right-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil. +func (cs *ContactSet) RightmostPoint() Vector { + + var right Vector + + for _, point := range cs.Points { + + if right == nil || point[0] > right[0] { + right = point + } + + } + + return right + +} + +// TopmostPoint returns the top-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil. +func (cs *ContactSet) TopmostPoint() Vector { + + var top Vector + + for _, point := range cs.Points { + + if top == nil || point[1] < top[1] { + top = point + } + + } + + return top + +} + +// BottommostPoint returns the bottom-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil. +func (cs *ContactSet) BottommostPoint() Vector { + + var bottom Vector + + for _, point := range cs.Points { + + if bottom == nil || point[1] > bottom[1] { + bottom = point + } + + } + + return bottom + +} + +// Intersection tests to see if a ConvexPolygon intersects with the other given Shape. dx and dy are delta movement variables indicating +// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it +// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding +// the intersection. +func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet { + + contactSet := NewContactSet() + + ogX := cp.X + ogY := cp.Y + cp.X += dx + cp.Y += dy + + if circle, isCircle := other.(*Circle); isCircle { + + for _, line := range cp.Lines() { + contactSet.Points = append(contactSet.Points, line.IntersectionPointsCircle(circle)...) + } + + } else if poly, isPoly := other.(*ConvexPolygon); isPoly { + + for _, line := range cp.Lines() { + + for _, otherLine := range poly.Lines() { + + if point := line.IntersectionPointsLine(otherLine); point != nil { + contactSet.Points = append(contactSet.Points, point) + } + + } + + } + + } + + if len(contactSet.Points) > 0 { + + for _, point := range contactSet.Points { + contactSet.Center = contactSet.Center.Add(point) + } + + contactSet.Center[0] /= float64(len(contactSet.Points)) + contactSet.Center[1] /= float64(len(contactSet.Points)) + + if mtv := cp.calculateMTV(contactSet, other); mtv != nil { + contactSet.MTV = mtv + } + + } else { + contactSet = nil + } + + // If dx or dy aren't 0, then the MTV will be greater to compensate; this adjusts the vector back. + if contactSet != nil && (dx != 0 || dy != 0) { + deltaMagnitude := Vector{dx, dy}.Magnitude() + ogMagnitude := contactSet.MTV.Magnitude() + contactSet.MTV = contactSet.MTV.Unit().Scale(ogMagnitude - deltaMagnitude) + } + + cp.X = ogX + cp.Y = ogY + + return contactSet + +} + +// calculateMTV returns the MTV, if possible, and a bool indicating whether it was possible or not. +func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) Vector { + + delta := Vector{0, 0} + + smallest := Vector{math.MaxFloat64, 0} + + switch other := otherShape.(type) { + + case *ConvexPolygon: + + for _, axis := range cp.SATAxes() { + if !cp.Project(axis).Overlapping(other.Project(axis)) { + return nil + } + + overlap := cp.Project(axis).Overlap(other.Project(axis)) + + if smallest.Magnitude() > overlap { + smallest = axis.Scale(overlap) + } + + } + + for _, axis := range other.SATAxes() { + + if !cp.Project(axis).Overlapping(other.Project(axis)) { + return nil + } + + overlap := cp.Project(axis).Overlap(other.Project(axis)) + + if smallest.Magnitude() > overlap { + smallest = axis.Scale(overlap) + } + + } + // Removed support of "Circle" to remove dependency of "sort" module + } + + delta[0] = smallest[0] + delta[1] = smallest[1] + + return delta +} + +// ContainedBy returns if the ConvexPolygon is wholly contained by the other shape provided. +func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool { + + switch other := otherShape.(type) { + + case *ConvexPolygon: + + for _, axis := range cp.SATAxes() { + if !cp.Project(axis).IsInside(other.Project(axis)) { + return false + } + } + + for _, axis := range other.SATAxes() { + if !cp.Project(axis).IsInside(other.Project(axis)) { + return false + } + } + + } + + return true +} + +// FlipH flips the ConvexPolygon's vertices horizontally according to their initial offset when adding the points. +func (cp *ConvexPolygon) FlipH() { + + for _, v := range cp.Points { + v[0] = -v[0] + } + // We have to reverse vertex order after flipping the vertices to ensure the winding order is consistent between Objects (so that the normals are consistently outside or inside, which is important + // when doing Intersection tests). If we assume that the normal of a line, going from vertex A to vertex B, is one direction, then the normal would be inverted if the vertices were flipped in position, + // but not in order. This would make Intersection tests drive objects into each other, instead of giving the delta to move away. + cp.ReverseVertexOrder() + +} + +// FlipV flips the ConvexPolygon's vertices vertically according to their initial offset when adding the points. +func (cp *ConvexPolygon) FlipV() { + + for _, v := range cp.Points { + v[1] = -v[1] + } + cp.ReverseVertexOrder() + +} + +// ReverseVertexOrder reverses the vertex ordering of the ConvexPolygon. +func (cp *ConvexPolygon) ReverseVertexOrder() { + + verts := []Vector{cp.Points[0]} + + for i := len(cp.Points) - 1; i >= 1; i-- { + verts = append(verts, cp.Points[i]) + } + + cp.Points = verts + +} + +// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own +// "thing" with its own optimized Intersection code check. +func NewRectangle(x, y, w, h float64) *ConvexPolygon { + return NewConvexPolygon( + x, y, + x+w, y, + x+w, y+h, + x, y+h, + ) +} + +type Circle struct { + X, Y, Radius float64 +} + +// NewCircle returns a new Circle, with its center at the X and Y position given, and with the defined radius. +func NewCircle(x, y, radius float64) *Circle { + circle := &Circle{ + X: x, + Y: y, + Radius: radius, + } + return circle +} + +func (circle *Circle) Clone() Shape { + return NewCircle(circle.X, circle.Y, circle.Radius) +} + +// Bounds returns the top-left and bottom-right corners of the Circle. +func (circle *Circle) Bounds() (Vector, Vector) { + return Vector{circle.X - circle.Radius, circle.Y - circle.Radius}, Vector{circle.X + circle.Radius, circle.Y + circle.Radius} +} + +// Intersection tests to see if a Circle intersects with the other given Shape. dx and dy are delta movement variables indicating +// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it +// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding +// the intersection. +func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet { + + var contactSet *ContactSet + + ox := circle.X + oy := circle.Y + + circle.X += dx + circle.Y += dy + + // here + + switch shape := other.(type) { + case *ConvexPolygon: + // Maybe this would work? + contactSet = shape.Intersection(-dx, -dy, circle) + if contactSet != nil { + contactSet.MTV = contactSet.MTV.Scale(-1) + } + case *Circle: + + contactSet = NewContactSet() + + contactSet.Points = circle.IntersectionPointsCircle(shape) + + if len(contactSet.Points) == 0 { + return nil + } + + contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y} + dist := contactSet.MTV.Magnitude() + contactSet.MTV = contactSet.MTV.Unit().Scale(circle.Radius + shape.Radius - dist) + + for _, point := range contactSet.Points { + contactSet.Center = contactSet.Center.Add(point) + } + + contactSet.Center[0] /= float64(len(contactSet.Points)) + contactSet.Center[1] /= float64(len(contactSet.Points)) + + // if contactSet != nil { + // contactSet.MTV[0] -= dx + // contactSet.MTV[1] -= dy + // } + + // contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y} + } + + circle.X = ox + circle.Y = oy + + return contactSet +} + +// Move translates the Circle by the designated X and Y values. +func (circle *Circle) Move(x, y float64) { + circle.X += x + circle.Y += y +} + +// MoveVec translates the Circle by the designated Vector. +func (circle *Circle) MoveVec(vec Vector) { + circle.X += vec.X() + circle.Y += vec.Y() +} + +// SetPosition sets the center position of the Circle using the X and Y values given. +func (circle *Circle) SetPosition(x, y float64) { + circle.X = x + circle.Y = y +} + +// SetPosition sets the center position of the Circle using the Vector given. +func (circle *Circle) SetPositionVec(vec Vector) { + circle.X = vec.X() + circle.Y = vec.Y() +} + +// Position() returns the X and Y position of the Circle. +func (circle *Circle) Position() (float64, float64) { + return circle.X, circle.Y +} + +// PointInside returns if the given Vector is inside of the circle. +func (circle *Circle) PointInside(point Vector) bool { + return point.Sub(Vector{circle.X, circle.Y}).Magnitude() <= circle.Radius +} + +// IntersectionPointsCircle returns the intersection points of the two circles provided. +func (circle *Circle) IntersectionPointsCircle(other *Circle) []Vector { + + d := math.Sqrt(math.Pow(other.X-circle.X, 2) + math.Pow(other.Y-circle.Y, 2)) + + if d > circle.Radius+other.Radius || d < math.Abs(circle.Radius-other.Radius) || d == 0 && circle.Radius == other.Radius { + return nil + } + + a := (math.Pow(circle.Radius, 2) - math.Pow(other.Radius, 2) + math.Pow(d, 2)) / (2 * d) + h := math.Sqrt(math.Pow(circle.Radius, 2) - math.Pow(a, 2)) + + x2 := circle.X + a*(other.X-circle.X)/d + y2 := circle.Y + a*(other.Y-circle.Y)/d + + return []Vector{ + {x2 + h*(other.Y-circle.Y)/d, y2 - h*(other.X-circle.X)/d}, + {x2 - h*(other.Y-circle.Y)/d, y2 + h*(other.X-circle.X)/d}, + } + +} + +type Projection struct { + Min, Max float64 +} + +// Overlapping returns whether a Projection is overlapping with the other, provided Projection. Credit to https://www.sevenson.com.au/programming/sat/ +func (projection Projection) Overlapping(other Projection) bool { + return projection.Overlap(other) > 0 +} + +// Overlap returns the amount that a Projection is overlapping with the other, provided Projection. Credit to https://dyn4j.org/2010/01/sat/#sat-nointer +func (projection Projection) Overlap(other Projection) float64 { + return math.Min(projection.Max, other.Max) - math.Max(projection.Min, other.Min) +} + +// IsInside returns whether the Projection is wholly inside of the other, provided Projection. +func (projection Projection) IsInside(other Projection) bool { + return projection.Min >= other.Min && projection.Max <= other.Max +} diff --git a/resolv_tailored/space.go b/resolv_tailored/space.go new file mode 100644 index 0000000..79642ca --- /dev/null +++ b/resolv_tailored/space.go @@ -0,0 +1,263 @@ +package resolv + +import ( + "math" +) + +// Space represents a collision space. Internally, each Space contains a 2D array of Cells, with each Cell being the same size. Cells contain information on which +// Objects occupy those spaces. +type Space struct { + Cells [][]*Cell + CellWidth, CellHeight int // Width and Height of each Cell in "world-space" / pixels / whatever +} + +// NewSpace creates a new Space. spaceWidth and spaceHeight is the width and height of the Space (usually in pixels), which is then populated with cells of size +// cellWidth by cellHeight. Generally, you want cells to be the size of the smallest collide-able objects in your game, and you want to move Objects at a maximum +// speed of one cell size per collision check to avoid missing any possible collisions. +func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space { + + sp := &Space{ + CellWidth: cellWidth, + CellHeight: cellHeight, + } + + sp.Resize(spaceWidth/cellWidth, spaceHeight/cellHeight) + + // sp.Resize(int(math.Ceil(float64(spaceWidth)/float64(cellWidth))), + // int(math.Ceil(float64(spaceHeight)/float64(cellHeight)))) + + return sp + +} + +// Add adds the specified Objects to the Space, updating the Space's cells to refer to the Object. +func (sp *Space) Add(objects ...*Object) { + + if sp == nil { + panic("ERROR: space is nil") + } + + for _, obj := range objects { + + obj.Space = sp + + // We call Update() once to make sure the object gets its cells added. + obj.Update() + + } + +} + +// Remove removes the specified Objects from being associated with the Space. This should be done whenever an Object is removed from the +// game. +func (sp *Space) Remove(objects ...*Object) { + + if sp == nil { + panic("ERROR: space is nil") + } + + for _, obj := range objects { + + for _, cell := range obj.TouchingCells { + cell.unregister(obj) + } + + obj.TouchingCells = []*Cell{} + + obj.Space = nil + + } + +} + +// Objects loops through all Cells in the Space (from top to bottom, and from left to right) to return all Objects +// that exist in the Space. Of course, each Object is counted only once. +func (sp *Space) Objects() []*Object { + + objectsAdded := map[*Object]bool{} + objects := []*Object{} + + for cy := range sp.Cells { + + for cx := range sp.Cells[cy] { + + for _, o := range sp.Cells[cy][cx].Objects { + + if _, added := objectsAdded[o]; !added { + objects = append(objects, o) + objectsAdded[o] = true + } + + } + + } + + } + + return objects + +} + +// Resize resizes the internal Cells array. +func (sp *Space) Resize(width, height int) { + + sp.Cells = [][]*Cell{} + + for y := 0; y < height; y++ { + + sp.Cells = append(sp.Cells, []*Cell{}) + + for x := 0; x < width; x++ { + sp.Cells[y] = append(sp.Cells[y], newCell(x, y)) + } + + } + +} + +// Cell returns the Cell at the given cellular / spatial (not world) X and Y position in the Space. If the X and Y position are +// out of bounds, Cell() will return nil. +func (sp *Space) Cell(x, y int) *Cell { + + if y >= 0 && y < len(sp.Cells) && x >= 0 && x < len(sp.Cells[y]) { + return sp.Cells[y][x] + } + return nil + +} + +// CheckCells checks a set of cells (from x,y to x + w, y + h in cellular coordinates) and return the first object within those Cells that contains any of the tags given. +// If no tags are given, then CheckCells will return the first Object found in any Cell. +func (sp *Space) CheckCells(x, y, w, h int, tags ...string) *Object { + + for ix := x; ix < x+w; ix++ { + + for iy := y; iy < y+h; iy++ { + + cell := sp.Cell(ix, iy) + + if cell != nil { + + if len(tags) > 0 { + + if cell.ContainsTags(tags...) { + for _, obj := range cell.Objects { + if obj.HasTags(tags...) { + return obj + } + } + } + + } else if cell.Occupied() { + return cell.Objects[0] + } + + } + + } + + } + + return nil + +} + +// CheckCellsWorld checks the cells of the Grid with the given world coordinates. +// Internally, this is just syntactic sugar for calling Space.WorldToSpace() on the +// position and size given. +func (sp *Space) CheckCellsWorld(x, y, w, h float64, tags ...string) *Object { + + sx, sy := sp.WorldToSpace(x, y) + cw, ch := sp.WorldToSpace(w, h) + + return sp.CheckCells(sx, sy, cw, ch, tags...) + +} + +// UnregisterAllObjects unregisters all Objects registered to Cells in the Space. +func (sp *Space) UnregisterAllObjects() { + + for y := 0; y < len(sp.Cells); y++ { + + for x := 0; x < len(sp.Cells[y]); x++ { + cell := sp.Cells[y][x] + sp.Remove(cell.Objects...) + } + + } + +} + +// WorldToSpace converts from a world position (x, y) to a position in the Space (a grid-based position). +func (sp *Space) WorldToSpace(x, y float64) (int, int) { + fx := int(math.Floor(x / float64(sp.CellWidth))) + fy := int(math.Floor(y / float64(sp.CellHeight))) + return fx, fy +} + +// SpaceToWorld converts from a position in the Space (on a grid) to a world-based position, given the size of the Space when first created. +func (sp *Space) SpaceToWorld(x, y int) (float64, float64) { + fx := float64(x * sp.CellWidth) + fy := float64(y * sp.CellHeight) + return fx, fy +} + +// Height returns the height of the Space grid in Cells (so a 320x240 Space with 16x16 cells would have a height of 15). +func (sp *Space) Height() int { + return len(sp.Cells) +} + +// Width returns the width of the Space grid in Cells (so a 320x240 Space with 16x16 cells would have a width of 20). +func (sp *Space) Width() int { + if len(sp.Cells) > 0 { + return len(sp.Cells[0]) + } + return 0 +} + +func (sp *Space) CellsInLine(startX, startY, endX, endY int) []*Cell { + + cells := []*Cell{} + cell := sp.Cell(startX, startY) + endCell := sp.Cell(endX, endY) + + if cell != nil && endCell != nil { + + dv := Vector{float64(endX - startX), float64(endY - startY)}.Unit() + dv[0] *= float64(sp.CellWidth / 2) + dv[1] *= float64(sp.CellHeight / 2) + + pX, pY := sp.SpaceToWorld(startX, startY) + p := Vector{pX + float64(sp.CellWidth/2), pY + float64(sp.CellHeight/2)} + + alternate := false + + for cell != nil { + + if cell == endCell { + cells = append(cells, cell) + break + } + + cells = append(cells, cell) + + if alternate { + p[1] += dv[1] + } else { + p[0] += dv[0] + } + + cx, cy := sp.WorldToSpace(p[0], p[1]) + c := sp.Cell(cx, cy) + if c != cell { + cell = c + } + alternate = !alternate + + } + + } + + return cells + +} diff --git a/resolv_tailored/vector.go b/resolv_tailored/vector.go new file mode 100644 index 0000000..346254f --- /dev/null +++ b/resolv_tailored/vector.go @@ -0,0 +1,281 @@ +package resolv + +import ( + "math" +) + +// Vector is the definition of a row vector that contains scalars as +// 64 bit floats +type Vector []float64 + +// Axis is an integer enum type that describes vector axis +type Axis int + +const ( + // the consts below are used to represent vector axis, they are useful + // to lookup values within the vector. + X Axis = iota + Y + Z +) + +// Clone a vector +func Clone(v Vector) Vector { + return v.Clone() +} + +// Clone a vector +func (v Vector) Clone() Vector { + clone := make(Vector, len(v)) + copy(clone, v) + return clone +} + +// Add a vector with a vector or a set of vectors +func Add(v1 Vector, vs ...Vector) Vector { + return v1.Clone().Add(vs...) +} + +// Add a vector with a vector or a set of vectors +func (v Vector) Add(vs ...Vector) Vector { + dim := len(v) + + for i := range vs { + if len(vs[i]) > dim { + axpyUnitaryTo(v, 1, v, vs[i][:dim]) + } else { + axpyUnitaryTo(v, 1, v, vs[i]) + } + } + + return v +} + +// Sub subtracts a vector with another vector or a set of vectors +func Sub(v1 Vector, vs ...Vector) Vector { + return v1.Clone().Sub(vs...) +} + +// Sub subtracts a vector with another vector or a set of vectors +func (v Vector) Sub(vs ...Vector) Vector { + dim := len(v) + + for i := range vs { + if len(vs[i]) > dim { + axpyUnitaryTo(v, -1, vs[i][:dim], v) + } else { + axpyUnitaryTo(v, -1, vs[i], v) + } + } + + return v +} + +// Scale vector with a given size +func Scale(v Vector, size float64) Vector { + return v.Clone().Scale(size) +} + +// Scale vector with a given size +func (v Vector) Scale(size float64) Vector { + scalUnitaryTo(v, size, v) + return v +} + +// Equal compares that two vectors are equal to each other +func Equal(v1, v2 Vector) bool { + return v1.Equal(v2) +} + +// Equal compares that two vectors are equal to each other +func (v Vector) Equal(v2 Vector) bool { + if len(v) != len(v2) { + return false + } + + for i := range v { + if math.Abs(v[i]-v2[i]) > 1e-8 { + return false + } + } + + return true +} + +// Magnitude of a vector +func Magnitude(v Vector) float64 { + return v.Magnitude() +} + +// Magnitude of a vector +func (v Vector) Magnitude() float64 { + return math.Sqrt(v.Magnitude2()) +} + +func (v Vector) Magnitude2() float64 { + var result float64 + + for _, scalar := range v { + result += scalar * scalar + } + + return result +} + +// Unit returns a direction vector with the length of one. +func Unit(v Vector) Vector { + return v.Clone().Unit() +} + +// Unit returns a direction vector with the length of one. +func (v Vector) Unit() Vector { + l := v.Magnitude() + + if l < 1e-8 { + return v + } + + for i := range v { + v[i] = v[i] / l + } + + return v +} + +// Dot product of two vectors +func Dot(v1, v2 Vector) float64 { + result, dim1, dim2 := 0., len(v1), len(v2) + + if dim1 > dim2 { + v2 = append(v2, make(Vector, dim1-dim2)...) + } + + if dim1 < dim2 { + v1 = append(v1, make(Vector, dim2-dim1)...) + } + + for i := range v1 { + result += v1[i] * v2[i] + } + + return result +} + +// Dot product of two vectors +func (v Vector) Dot(v2 Vector) float64 { + return Dot(v, v2) +} + +// Cross product of two vectors +func Cross(v1, v2 Vector) Vector { + return v1.Cross(v2) +} + +// Cross product of two vectors +func (v Vector) Cross(v2 Vector) Vector { + if len(v) != 3 || len(v2) != 3 { + return nil + } + + return Vector{ + v[Y]*v2[Z] - v[Z]*v2[Y], + v[Z]*v2[X] - v[X]*v2[Z], + v[X]*v2[Z] - v[Z]*v2[X], + } +} + +// Rotate is rotating a vector around a specified axis. +// If no axis are specified, it will default to the Z axis. +// +// If a vector with more than 3-dimensions is rotated, it will cut the extra +// dimensions and return a 3-dimensional vector. +// +// NOTE: the ...Axis is just syntactic sugar that allows the axis to not be +// specified and default to Z, if multiple axis is passed the first will be +// set as the rotation axis +func Rotate(v Vector, angle float64, as ...Axis) Vector { + return v.Clone().Rotate(angle, as...) +} + +// Rotate is rotating a vector around a specified axis. +// If no axis are specified, it will default to the Z axis. +// +// If a vector with more than 3-dimensions is rotated, it will cut the extra +// dimensions and return a 3-dimensional vector. +// +// NOTE: the ...Axis is just syntactic sugar that allows the axis to not be +// specified and default to Z, if multiple axis is passed the first will be +// set as the rotation axis +func (v Vector) Rotate(angle float64, as ...Axis) Vector { + axis, dim := Z, len(v) + + if dim == 0 { + return v + } + + if len(as) > 0 { + axis = as[0] + } + + if dim == 1 && axis != Z { + v = append(v, 0, 0) + } + + if (dim < 2 && axis == Z) || (dim == 2 && axis != Z) { + v = append(v, 0) + } + + x, y := v[X], v[Y] + + cos, sin := math.Cos(angle), math.Sin(angle) + + switch axis { + case X: + z := v[Z] + v[Y] = y*cos - z*sin + v[Z] = y*sin + z*cos + case Y: + z := v[Z] + v[X] = x*cos + z*sin + v[Z] = -x*sin + z*cos + case Z: + v[X] = x*cos - y*sin + v[Y] = x*sin + y*cos + } + + if dim > 3 { + return v[:3] + } + + return v +} + +// X is corresponding to doing a v[0] lookup, if index 0 does not exist yet, a +// 0 will be returned instead +func (v Vector) X() float64 { + if len(v) < 1 { + return 0. + } + + return v[X] +} + +// Y is corresponding to doing a v[1] lookup, if index 1 does not exist yet, a +// 0 will be returned instead +func (v Vector) Y() float64 { + if len(v) < 2 { + return 0. + } + + return v[Y] +} + +// Z is corresponding to doing a v[2] lookup, if index 2 does not exist yet, a +// 0 will be returned instead +func (v Vector) Z() float64 { + if len(v) < 3 { + return 0. + } + + return v[Z] +}