Preparation of server-side collision calc.

This commit is contained in:
genxium 2022-09-26 23:09:18 +08:00
parent ff092a40ed
commit 14fb8e94b2
13 changed files with 270 additions and 3976 deletions

View File

@ -7,7 +7,6 @@ import (
type Barrier struct { type Barrier struct {
X float64 X float64
Y float64 Y float64
Type uint32
Boundary *Polygon2D Boundary *Polygon2D
CollidableBody *box2d.B2Body CollidableBody *box2d.B2Body
} }

View File

@ -1,19 +0,0 @@
package models
import (
"github.com/ByteArena/box2d"
)
type Bullet struct {
LocalIdInBattle int32 `json:"-"`
LinearSpeed float64 `json:"-"`
X float64 `json:"-"`
Y float64 `json:"-"`
Removed bool `json:"-"`
Dir *Direction `json:"-"`
StartAtPoint *Vec2D `json:"-"`
EndAtPoint *Vec2D `json:"-"`
DamageBoundary *Polygon2D `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
RemovedAtFrameId int32 `json:"-"`
}

View File

@ -90,114 +90,3 @@ func toPbPlayers(modelInstances map[int32]*Player) map[int32]*pb.Player {
return toRet return toRet
} }
func toPbTreasures(modelInstances map[int32]*Treasure) map[int32]*pb.Treasure {
toRet := make(map[int32]*pb.Treasure, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
toRet[k] = &pb.Treasure{
Id: last.Id,
LocalIdInBattle: last.LocalIdInBattle,
Score: last.Score,
X: last.X,
Y: last.Y,
Removed: last.Removed,
Type: last.Type,
}
}
return toRet
}
func toPbTraps(modelInstances map[int32]*Trap) map[int32]*pb.Trap {
toRet := make(map[int32]*pb.Trap, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
toRet[k] = &pb.Trap{
Id: last.Id,
LocalIdInBattle: last.LocalIdInBattle,
X: last.X,
Y: last.Y,
Removed: last.Removed,
Type: last.Type,
}
}
return toRet
}
func toPbBullets(modelInstances map[int32]*Bullet) map[int32]*pb.Bullet {
toRet := make(map[int32]*pb.Bullet, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
if nil == last.StartAtPoint || nil == last.EndAtPoint {
continue
}
toRet[k] = &pb.Bullet{
LocalIdInBattle: last.LocalIdInBattle,
LinearSpeed: last.LinearSpeed,
X: last.X,
Y: last.Y,
Removed: last.Removed,
StartAtPoint: &pb.Vec2D{
X: last.StartAtPoint.X,
Y: last.StartAtPoint.Y,
},
EndAtPoint: &pb.Vec2D{
X: last.EndAtPoint.X,
Y: last.EndAtPoint.Y,
},
}
}
return toRet
}
func toPbSpeedShoes(modelInstances map[int32]*SpeedShoe) map[int32]*pb.SpeedShoe {
toRet := make(map[int32]*pb.SpeedShoe, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
toRet[k] = &pb.SpeedShoe{
Id: last.Id,
LocalIdInBattle: last.LocalIdInBattle,
X: last.X,
Y: last.Y,
Removed: last.Removed,
Type: last.Type,
}
}
return toRet
}
func toPbGuardTowers(modelInstances map[int32]*GuardTower) map[int32]*pb.GuardTower {
toRet := make(map[int32]*pb.GuardTower, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
toRet[k] = &pb.GuardTower{
Id: last.Id,
LocalIdInBattle: last.LocalIdInBattle,
X: last.X,
Y: last.Y,
Removed: last.Removed,
Type: last.Type,
}
}
return toRet
}

View File

@ -1,14 +0,0 @@
package models
import "github.com/ByteArena/box2d"
type Pumpkin struct {
LocalIdInBattle int32 `json:"localIdInBattle,omitempty"`
LinearSpeed float64 `json:"linearSpeed,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Removed bool `json:"removed,omitempty"`
Dir *Direction `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
RemovedAtFrameId int32 `json:"-"`
}

View File

@ -43,20 +43,10 @@ const (
const ( const (
// You can equivalently use the `GroupIndex` approach, but the more complicated and general purpose approach is used deliberately here. Reference http://www.aurelienribon.com/post/2011-07-box2d-tutorial-collision-filtering. // You can equivalently use the `GroupIndex` approach, but the more complicated and general purpose approach is used deliberately here. Reference http://www.aurelienribon.com/post/2011-07-box2d-tutorial-collision-filtering.
COLLISION_CATEGORY_CONTROLLED_PLAYER = (1 << 1) COLLISION_CATEGORY_CONTROLLED_PLAYER = (1 << 1)
COLLISION_CATEGORY_TREASURE = (1 << 2) COLLISION_CATEGORY_BARRIER = (1 << 2)
COLLISION_CATEGORY_TRAP = (1 << 3)
COLLISION_CATEGORY_TRAP_BULLET = (1 << 4)
COLLISION_CATEGORY_BARRIER = (1 << 5)
COLLISION_CATEGORY_PUMPKIN = (1 << 6)
COLLISION_CATEGORY_SPEED_SHOES = (1 << 7)
COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_TREASURE | COLLISION_CATEGORY_TRAP | COLLISION_CATEGORY_TRAP_BULLET | COLLISION_CATEGORY_SPEED_SHOES) COLLISION_MASK_FOR_CONTROLLED_PLAYER = (COLLISION_CATEGORY_BARRIER)
COLLISION_MASK_FOR_TREASURE = (COLLISION_CATEGORY_CONTROLLED_PLAYER) COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
COLLISION_MASK_FOR_TRAP = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
COLLISION_MASK_FOR_TRAP_BULLET = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
COLLISION_MASK_FOR_BARRIER = (COLLISION_CATEGORY_PUMPKIN)
COLLISION_MASK_FOR_PUMPKIN = (COLLISION_CATEGORY_BARRIER)
COLLISION_MASK_FOR_SPEED_SHOES = (COLLISION_CATEGORY_CONTROLLED_PLAYER)
) )
var DIRECTION_DECODER = [][]int32{ var DIRECTION_DECODER = [][]int32{
@ -75,6 +65,22 @@ var DIRECTION_DECODER = [][]int32{
{0, -1}, {0, -1},
} }
var DIRECTION_DECODER_INVERSE_LENGTH = []float32{
0.0,
1.0,
1.0,
0.5,
0.5,
0.4472,
0.4472,
0.4472,
0.4472,
0.5,
0.5,
1.0,
1.0,
}
type RoomBattleState struct { type RoomBattleState struct {
IDLE int32 IDLE int32
WAITING int32 WAITING int32
@ -142,16 +148,11 @@ type Room struct {
BattleDurationNanos int64 BattleDurationNanos int64
EffectivePlayerCount int32 EffectivePlayerCount int32
DismissalWaitGroup sync.WaitGroup DismissalWaitGroup sync.WaitGroup
Treasures map[int32]*Treasure
Traps map[int32]*Trap
GuardTowers map[int32]*GuardTower
Bullets map[int32]*Bullet
SpeedShoes map[int32]*SpeedShoe
Barriers map[int32]*Barrier Barriers map[int32]*Barrier
Pumpkins map[int32]*Pumpkin
AccumulatedLocalIdForBullets int32 AccumulatedLocalIdForBullets int32
CollidableWorld *box2d.B2World CollidableWorld *box2d.B2World
AllPlayerInputsBuffer *RingBuffer AllPlayerInputsBuffer *RingBuffer
RenderFrameBuffer *RingBuffer
LastAllConfirmedInputFrameId int32 LastAllConfirmedInputFrameId int32
LastAllConfirmedInputFrameIdWithChange int32 LastAllConfirmedInputFrameIdWithChange int32
LastAllConfirmedInputList []uint64 LastAllConfirmedInputList []uint64
@ -168,58 +169,11 @@ type Room struct {
RawBattleStrToPolygon2DListMap StrToPolygon2DListMap RawBattleStrToPolygon2DListMap StrToPolygon2DListMap
} }
func (pR *Room) onTreasurePickedUp(contactingPlayer *Player, contactingTreasure *Treasure) {
if _, existent := pR.Treasures[contactingTreasure.LocalIdInBattle]; existent {
Logger.Info("Player has picked up treasure:", zap.Any("roomId", pR.Id), zap.Any("contactingPlayer.Id", contactingPlayer.Id), zap.Any("contactingTreasure.LocalIdInBattle", contactingTreasure.LocalIdInBattle))
pR.CollidableWorld.DestroyBody(contactingTreasure.CollidableBody)
pR.Treasures[contactingTreasure.LocalIdInBattle] = &Treasure{Removed: true}
pR.Players[contactingPlayer.Id].Score += contactingTreasure.Score
}
}
const ( const (
PLAYER_DEFAULT_SPEED = 200 // Hardcoded PLAYER_DEFAULT_SPEED = 200 // Hardcoded
ADD_SPEED = 100 // Hardcoded ADD_SPEED = 100 // Hardcoded
) )
func (pR *Room) onSpeedShoePickedUp(contactingPlayer *Player, contactingSpeedShoe *SpeedShoe, nowMillis int64) {
if _, existent := pR.SpeedShoes[contactingSpeedShoe.LocalIdInBattle]; existent && contactingPlayer.AddSpeedAtGmtMillis == -1 {
Logger.Info("Player has picked up a SpeedShoe:", zap.Any("roomId", pR.Id), zap.Any("contactingPlayer.Id", contactingPlayer.Id), zap.Any("contactingSpeedShoe.LocalIdInBattle", contactingSpeedShoe.LocalIdInBattle))
pR.CollidableWorld.DestroyBody(contactingSpeedShoe.CollidableBody)
pR.SpeedShoes[contactingSpeedShoe.LocalIdInBattle] = &SpeedShoe{
Removed: true,
RemovedAtFrameId: pR.Tick,
}
pR.Players[contactingPlayer.Id].Speed += ADD_SPEED
pR.Players[contactingPlayer.Id].AddSpeedAtGmtMillis = nowMillis
}
}
func (pR *Room) onBulletCrashed(contactingPlayer *Player, contactingBullet *Bullet, nowMillis int64, maxMillisToFreezePerPlayer int64) {
if _, existent := pR.Bullets[contactingBullet.LocalIdInBattle]; existent {
pR.CollidableWorld.DestroyBody(contactingBullet.CollidableBody)
pR.Bullets[contactingBullet.LocalIdInBattle] = &Bullet{
Removed: true,
RemovedAtFrameId: pR.Tick,
}
if contactingPlayer != nil {
if maxMillisToFreezePerPlayer > (nowMillis - pR.Players[contactingPlayer.Id].FrozenAtGmtMillis) {
// Deliberately doing nothing. -- YFLu, 2019-09-04.
} else {
pR.Players[contactingPlayer.Id].Speed = 0
pR.Players[contactingPlayer.Id].FrozenAtGmtMillis = nowMillis
pR.Players[contactingPlayer.Id].AddSpeedAtGmtMillis = -1
//Logger.Info("Player has picked up bullet:", zap.Any("roomId", pR.Id), zap.Any("contactingPlayer.Id", contactingPlayer.Id), zap.Any("contactingBullet.LocalIdInBattle", contactingBullet.LocalIdInBattle), zap.Any("pR.Players[contactingPlayer.Id].Speed", pR.Players[contactingPlayer.Id].Speed))
}
}
}
}
func (pR *Room) onPumpkinEncounterPlayer(pumpkin *Pumpkin, player *Player) {
Logger.Info("pumpkin has caught the player: ", zap.Any("pumpkinId", pumpkin.LocalIdInBattle), zap.Any("playerId", player.Id))
}
func (pR *Room) updateScore() { func (pR *Room) updateScore() {
pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State) pR.Score = calRoomScore(pR.EffectivePlayerCount, pR.Capacity, pR.State)
} }
@ -280,251 +234,6 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
return true return true
} }
func (pR *Room) refreshColliders() {
/*
"BarrierCollider"s are NOT added to the "colliders in B2World of the current battle", thus NOT involved in server-side collision detection!
-- YFLu, 2019-09-04
*/
gravity := box2d.MakeB2Vec2(0.0, 0.0)
world := box2d.MakeB2World(gravity)
world.SetContactFilter(&box2d.B2ContactFilter{})
pR.CollidableWorld = &world
Logger.Info("Begins `refreshColliders` for players:", zap.Any("roomId", pR.Id))
for _, player := range pR.Players {
var bdDef box2d.B2BodyDef
colliderOffset := box2d.MakeB2Vec2(0, 0) // Matching that of client-side setting.
bdDef = box2d.MakeB2BodyDef()
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef.Position.Set(player.X+colliderOffset.X, player.Y+colliderOffset.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
b2CircleShape := box2d.MakeB2CircleShape()
b2CircleShape.M_radius = 32 // Matching that of client-side setting.
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2CircleShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_CONTROLLED_PLAYER
fd.Filter.MaskBits = COLLISION_MASK_FOR_CONTROLLED_PLAYER
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
player.CollidableBody = b2Body
b2Body.SetUserData(player)
}
Logger.Info("Ends `refreshColliders` for players:", zap.Any("roomId", pR.Id))
Logger.Info("Begins `refreshColliders` for treasures:", zap.Any("roomId", pR.Id))
for _, treasure := range pR.Treasures {
var bdDef box2d.B2BodyDef
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef = box2d.MakeB2BodyDef()
bdDef.Position.Set(treasure.PickupBoundary.Anchor.X, treasure.PickupBoundary.Anchor.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
pointsCount := len(treasure.PickupBoundary.Points)
b2Vertices := make([]box2d.B2Vec2, pointsCount)
for vIndex, v2 := range treasure.PickupBoundary.Points {
b2Vertices[vIndex] = v2.ToB2Vec2()
}
b2PolygonShape := box2d.MakeB2PolygonShape()
b2PolygonShape.Set(b2Vertices, pointsCount)
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2PolygonShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_TREASURE
fd.Filter.MaskBits = COLLISION_MASK_FOR_TREASURE
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
treasure.CollidableBody = b2Body
b2Body.SetUserData(treasure)
}
Logger.Info("Ends `refreshColliders` for treasures:", zap.Any("roomId", pR.Id))
Logger.Info("Begins `refreshColliders` for towers:", zap.Any("roomId", pR.Id))
for _, tower := range pR.GuardTowers {
// Logger.Info("Begins `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower.LocalIdInBattle", tower.LocalIdInBattle), zap.Any("tower.X", tower.X), zap.Any("tower.Y", tower.Y), zap.Any("tower.PickupBoundary", tower.PickupBoundary), zap.Any("tower.PickupBoundary.Points", tower.PickupBoundary.Points), zap.Any("tower.WidthInB2World", tower.WidthInB2World), zap.Any("tower.HeightInB2World", tower.HeightInB2World), zap.Any("roomId", pR.Id))
var bdDef box2d.B2BodyDef
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef = box2d.MakeB2BodyDef()
bdDef.Position.Set(tower.PickupBoundary.Anchor.X, tower.PickupBoundary.Anchor.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
// Logger.Info("Checks#1 `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
pointsCount := len(tower.PickupBoundary.Points)
b2Vertices := make([]box2d.B2Vec2, pointsCount)
for vIndex, v2 := range tower.PickupBoundary.Points {
b2Vertices[vIndex] = v2.ToB2Vec2()
}
// Logger.Info("Checks#2 `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
b2PolygonShape := box2d.MakeB2PolygonShape()
// Logger.Info("Checks#3 `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
b2PolygonShape.Set(b2Vertices, pointsCount)
// Logger.Info("Checks#4 `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2PolygonShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_TRAP
fd.Filter.MaskBits = COLLISION_MASK_FOR_TRAP
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
// Logger.Info("Checks#5 `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
tower.CollidableBody = b2Body
b2Body.SetUserData(tower)
// Logger.Info("Ends `refreshColliders` for single tower:", zap.Any("k-th", k), zap.Any("tower", tower), zap.Any("roomId", pR.Id))
}
Logger.Info("Ends `refreshColliders` for towers:", zap.Any("roomId", pR.Id))
listener := RoomBattleContactListener{
name: "TreasureHunterX",
room: pR,
}
/*
* Setting a "ContactListener" for "pR.CollidableWorld"
* will only trigger corresponding callbacks in the
* SAME GOROUTINE of "pR.CollidableWorld.Step(...)" according
* to "https://github.com/ByteArena/box2d/blob/master/DynamicsB2World.go" and
* "https://github.com/ByteArena/box2d/blob/master/DynamicsB2Contact.go".
*
* The invocation-chain involves "Step -> SolveTOI -> B2ContactUpdate -> [BeginContact, EndContact, PreSolve]".
*/
pR.CollidableWorld.SetContactListener(listener)
}
func calculateDiffFrame(currentFrame *pb.RoomDownsyncFrame, lastFrame *pb.RoomDownsyncFrame) *pb.RoomDownsyncFrame {
if lastFrame == nil {
return currentFrame
}
diffFrame := &pb.RoomDownsyncFrame{
Id: currentFrame.Id,
RefFrameId: lastFrame.Id,
Players: currentFrame.Players,
SentAt: currentFrame.SentAt,
CountdownNanos: currentFrame.CountdownNanos,
Bullets: currentFrame.Bullets,
Treasures: make(map[int32]*pb.Treasure, 0),
Traps: make(map[int32]*pb.Trap, 0),
SpeedShoes: make(map[int32]*pb.SpeedShoe, 0),
GuardTowers: make(map[int32]*pb.GuardTower, 0),
}
for k, last := range lastFrame.Treasures {
if last.Removed {
diffFrame.Treasures[k] = last
continue
}
curr, ok := currentFrame.Treasures[k]
if !ok {
diffFrame.Treasures[k] = &pb.Treasure{Removed: true}
Logger.Info("A treasure is removed.", zap.Any("diffFrame.id", diffFrame.Id), zap.Any("treasure.LocalIdInBattle", curr.LocalIdInBattle))
continue
}
if ok, v := diffTreasure(last, curr); ok {
diffFrame.Treasures[k] = v
}
}
for k, last := range lastFrame.Bullets {
curr, ok := currentFrame.Bullets[k]
/*
* The use of 'bullet.RemovedAtFrameId' implies that you SHOULDN'T create a record '&Bullet{Removed: true}' here after it's already deleted from 'room.Bullets'. Same applies for `Traps` and `SpeedShoes`.
*
* -- YFLu
*/
if false == ok {
diffFrame.Bullets[k] = &pb.Bullet{Removed: true}
// Logger.Info("A bullet is removed.", zap.Any("diffFrame.id", diffFrame.Id), zap.Any("bullet.LocalIdInBattle", lastFrame.Bullets[k].LocalIdInBattle))
continue
}
if ok, v := diffBullet(last, curr); ok {
diffFrame.Bullets[k] = v
}
}
for k, last := range lastFrame.Traps {
curr, ok := currentFrame.Traps[k]
if false == ok {
continue
}
if ok, v := diffTrap(last, curr); ok {
diffFrame.Traps[k] = v
}
}
for k, last := range lastFrame.SpeedShoes {
curr, ok := currentFrame.SpeedShoes[k]
if false == ok {
continue
}
if ok, v := diffSpeedShoe(last, curr); ok {
diffFrame.SpeedShoes[k] = v
}
}
return diffFrame
}
func diffTreasure(last *pb.Treasure, curr *pb.Treasure) (bool, *pb.Treasure) {
treature := &pb.Treasure{}
t := false
if last.Score != curr.Score {
treature.Score = curr.Score
t = true
}
if last.X != curr.X {
treature.X = curr.X
t = true
}
if last.Y != curr.Y {
treature.Y = curr.Y
t = true
}
return t, treature
}
func diffTrap(last *pb.Trap, curr *pb.Trap) (bool, *pb.Trap) {
trap := &pb.Trap{}
t := false
if last.X != curr.X {
trap.X = curr.X
t = true
}
if last.Y != curr.Y {
trap.Y = curr.Y
t = true
}
return t, trap
}
func diffSpeedShoe(last *pb.SpeedShoe, curr *pb.SpeedShoe) (bool, *pb.SpeedShoe) {
speedShoe := &pb.SpeedShoe{}
t := false
if last.X != curr.X {
speedShoe.X = curr.X
t = true
}
if last.Y != curr.Y {
speedShoe.Y = curr.Y
t = true
}
return t, speedShoe
}
func diffBullet(last *pb.Bullet, curr *pb.Bullet) (bool, *pb.Bullet) {
t := true
return t, curr
}
func (pR *Room) ChooseStage() error { func (pR *Room) ChooseStage() error {
/* /*
* We use the verb "refresh" here to imply that upon invocation of this function, all colliders will be recovered if they were destroyed in the previous battle. * We use the verb "refresh" here to imply that upon invocation of this function, all colliders will be recovered if they were destroyed in the previous battle.
@ -592,81 +301,21 @@ func (pR *Room) ChooseStage() error {
pR.RawBattleStrToVec2DListMap = toRetStrToVec2DListMap pR.RawBattleStrToVec2DListMap = toRetStrToVec2DListMap
pR.RawBattleStrToPolygon2DListMap = toRetStrToPolygon2DListMap pR.RawBattleStrToPolygon2DListMap = toRetStrToPolygon2DListMap
// Refresh "Treasure" data for RoomDownsyncFrame. barrierPolygon2DList := *(toRetStrToPolygon2DListMap["Barrier"])
lowScoreTreasurePolygon2DList := *(toRetStrToPolygon2DListMap["LowScoreTreasure"])
highScoreTreasurePolygon2DList := *(toRetStrToPolygon2DListMap["HighScoreTreasure"])
var treasureLocalIdInBattle int32 = 0 var barrierLocalIdInBattle int32 = 0
for _, polygon2D := range lowScoreTreasurePolygon2DList { for _, polygon2D := range barrierPolygon2DList {
/* /*
// For debug-printing only. // For debug-printing only.
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
Logger.Info("ChooseStage printing polygon2D for lowScoreTreasurePolygon2DList", zap.Any("treasureLocalIdInBattle", treasureLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
*/ */
pR.Barriers[barrierLocalIdInBattle] = &Barrier{
theTreasure := &Treasure{
Id: 0,
LocalIdInBattle: treasureLocalIdInBattle,
Score: LOW_SCORE_TREASURE_SCORE,
Type: LOW_SCORE_TREASURE_TYPE,
X: polygon2D.Anchor.X, X: polygon2D.Anchor.X,
Y: polygon2D.Anchor.Y, Y: polygon2D.Anchor.Y,
PickupBoundary: polygon2D, Boundary: polygon2D,
} }
pR.Treasures[theTreasure.LocalIdInBattle] = theTreasure barrierLocalIdInBattle++
treasureLocalIdInBattle++
}
for _, polygon2D := range highScoreTreasurePolygon2DList {
/*
// For debug-printing only.
Logger.Info("ChooseStage printing polygon2D for highScoreTreasurePolygon2DList", zap.Any("treasureLocalIdInBattle", treasureLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
*/
theTreasure := &Treasure{
Id: 0,
LocalIdInBattle: treasureLocalIdInBattle,
Score: HIGH_SCORE_TREASURE_SCORE,
Type: HIGH_SCORE_TREASURE_TYPE,
X: polygon2D.Anchor.X,
Y: polygon2D.Anchor.Y,
PickupBoundary: polygon2D,
}
pR.Treasures[theTreasure.LocalIdInBattle] = theTreasure
treasureLocalIdInBattle++
}
// Refresh "GuardTower" data for RoomDownsyncFrame.
guardTowerPolygon2DList := *(toRetStrToPolygon2DListMap["GuardTower"])
var guardTowerLocalIdInBattle int32 = 0
for _, polygon2D := range guardTowerPolygon2DList {
/*
// For debug-printing only.
Logger.Info("ChooseStage printing polygon2D for guardTowerPolygon2DList", zap.Any("guardTowerLocalIdInBattle", guardTowerLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points), zap.Any("pR.GuardTowers", pR.GuardTowers))
*/
var inRangePlayers InRangePlayerCollection
pInRangePlayers := &inRangePlayers
pInRangePlayers = pInRangePlayers.Init(10)
theGuardTower := &GuardTower{
Id: 0,
LocalIdInBattle: guardTowerLocalIdInBattle,
X: polygon2D.Anchor.X,
Y: polygon2D.Anchor.Y,
PickupBoundary: polygon2D,
InRangePlayers: pInRangePlayers,
LastAttackTick: utils.UnixtimeNano(),
WidthInB2World: float64(polygon2D.TmxObjectWidth),
HeightInB2World: float64(polygon2D.TmxObjectHeight),
}
pR.GuardTowers[theGuardTower.LocalIdInBattle] = theGuardTower
guardTowerLocalIdInBattle++
} }
return nil return nil
@ -754,11 +403,6 @@ func (pR *Room) StartBattle() {
Id: pR.Tick, Id: pR.Tick,
RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START, RefFrameId: MAGIC_ROOM_DOWNSYNC_FRAME_ID_BATTLE_START,
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
Treasures: toPbTreasures(pR.Treasures),
Traps: toPbTraps(pR.Traps),
Bullets: toPbBullets(pR.Bullets),
SpeedShoes: toPbSpeedShoes(pR.SpeedShoes),
GuardTowers: toPbGuardTowers(pR.GuardTowers),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
CountdownNanos: (pR.BattleDurationNanos - totalElapsedNanos), CountdownNanos: (pR.BattleDurationNanos - totalElapsedNanos),
} }
@ -783,6 +427,8 @@ func (pR *Room) StartBattle() {
} }
stCalculation := utils.UnixtimeNano() stCalculation := utils.UnixtimeNano()
// TODO: Force confirm some non-all-confirmed but outdated input frames, derive server-sider RenderFrameBuffer elements from them, and send to respective players with "RoomDownsyncFrame.refFrameId=MAGIC_ROOM_DOWNSYNC_FRAME_ID_PLAYER_READDED_AND_ACKED"
refInputFrameId := int32(999999999) // Hardcoded as a max reference. refInputFrameId := int32(999999999) // Hardcoded as a max reference.
for playerId, _ := range pR.Players { for playerId, _ := range pR.Players {
thatId := atomic.LoadInt32(&(pR.Players[playerId].AckingInputFrameId)) thatId := atomic.LoadInt32(&(pR.Players[playerId].AckingInputFrameId))
@ -981,8 +627,6 @@ func (pR *Room) StopBattleForSettlement() {
Players: toPbPlayers(pR.Players), Players: toPbPlayers(pR.Players),
SentAt: utils.UnixtimeMilli(), SentAt: utils.UnixtimeMilli(),
CountdownNanos: -1, // TODO: Replace this magic constant! CountdownNanos: -1, // TODO: Replace this magic constant!
Treasures: toPbTreasures(pR.Treasures),
Traps: toPbTraps(pR.Traps),
} }
pR.sendSafely(assembledFrame, playerId) pR.sendSafely(assembledFrame, playerId)
} }
@ -1089,11 +733,6 @@ func (pR *Room) onDismissed() {
// Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference. // Always instantiates new HeapRAM blocks and let the old blocks die out due to not being retained by any root reference.
pR.Players = make(map[int32]*Player) pR.Players = make(map[int32]*Player)
pR.Treasures = make(map[int32]*Treasure)
pR.Traps = make(map[int32]*Trap)
pR.GuardTowers = make(map[int32]*GuardTower)
pR.Bullets = make(map[int32]*Bullet)
pR.SpeedShoes = make(map[int32]*SpeedShoe)
pR.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn) pR.PlayerDownsyncSessionDict = make(map[int32]*websocket.Conn)
pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType) pR.PlayerSignalToCloseDict = make(map[int32]SignalToCloseConnCbType)
@ -1105,6 +744,7 @@ func (pR *Room) onDismissed() {
pR.JoinIndexBooleanArr[indice] = false pR.JoinIndexBooleanArr[indice] = false
} }
pR.AllPlayerInputsBuffer = NewRingBuffer(1024) pR.AllPlayerInputsBuffer = NewRingBuffer(1024)
pR.RenderFrameBuffer = NewRingBuffer(1024)
pR.ChooseStage() pR.ChooseStage()
pR.EffectivePlayerCount = 0 pR.EffectivePlayerCount = 0
@ -1116,14 +756,6 @@ func (pR *Room) onDismissed() {
Logger.Info("The room is completely dismissed:", zap.Any("roomId", pR.Id)) Logger.Info("The room is completely dismissed:", zap.Any("roomId", pR.Id))
} }
func (pR *Room) Unicast(toPlayerId int32, msg interface{}) {
// TODO
}
func (pR *Room) Broadcast(msg interface{}) {
// TODO
}
func (pR *Room) expelPlayerDuringGame(playerId int32) { func (pR *Room) expelPlayerDuringGame(playerId int32) {
defer pR.onPlayerExpelledDuringGame(playerId) defer pR.onPlayerExpelledDuringGame(playerId)
} }
@ -1368,6 +1000,84 @@ func (pR *Room) inputFrameIdDebuggable(inputFrameId int32) bool {
return 0 == (inputFrameId % 10) return 0 == (inputFrameId % 10)
} }
func (pR *Room) refreshColliders() {
gravity := box2d.MakeB2Vec2(0.0, 0.0)
world := box2d.MakeB2World(gravity)
world.SetContactFilter(&box2d.B2ContactFilter{})
pR.CollidableWorld = &world
Logger.Info("Begins players collider processing:", zap.Any("roomId", pR.Id))
for _, player := range pR.Players {
var bdDef box2d.B2BodyDef
colliderOffset := box2d.MakeB2Vec2(0, 0) // Matching that of client-side setting.
bdDef = box2d.MakeB2BodyDef()
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef.Position.Set(player.X+colliderOffset.X, player.Y+colliderOffset.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
b2CircleShape := box2d.MakeB2CircleShape()
b2CircleShape.M_radius = 32 // Matching that of client-side setting.
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2CircleShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_CONTROLLED_PLAYER
fd.Filter.MaskBits = COLLISION_MASK_FOR_CONTROLLED_PLAYER
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
player.CollidableBody = b2Body
b2Body.SetUserData(player)
}
Logger.Info("Ends players collider processing:", zap.Any("roomId", pR.Id))
Logger.Info("Begins barriers collider processing:", zap.Any("roomId", pR.Id))
for _, barrier := range pR.Barriers {
var bdDef box2d.B2BodyDef
bdDef.Type = box2d.B2BodyType.B2_dynamicBody
bdDef = box2d.MakeB2BodyDef()
bdDef.Position.Set(barrier.Boundary.Anchor.X, barrier.Boundary.Anchor.Y)
b2Body := pR.CollidableWorld.CreateBody(&bdDef)
pointsCount := len(barrier.Boundary.Points)
b2Vertices := make([]box2d.B2Vec2, pointsCount)
for vIndex, v2 := range barrier.Boundary.Points {
b2Vertices[vIndex] = v2.ToB2Vec2()
}
b2PolygonShape := box2d.MakeB2PolygonShape()
b2PolygonShape.Set(b2Vertices, pointsCount)
fd := box2d.MakeB2FixtureDef()
fd.Shape = &b2PolygonShape
fd.Filter.CategoryBits = COLLISION_CATEGORY_BARRIER
fd.Filter.MaskBits = COLLISION_MASK_FOR_BARRIER
fd.Density = 0.0
b2Body.CreateFixtureFromDef(&fd)
barrier.CollidableBody = b2Body
b2Body.SetUserData(barrier)
}
Logger.Info("Ends barriers collider processing:", zap.Any("roomId", pR.Id))
listener := RoomBattleContactListener{
name: "DelayNoMore",
room: pR,
}
/*
* Setting a "ContactListener" for "pR.CollidableWorld"
* will only trigger corresponding callbacks in the
* SAME GOROUTINE of "pR.CollidableWorld.Step(...)" according
* to "https://github.com/ByteArena/box2d/blob/master/DynamicsB2World.go" and
* "https://github.com/ByteArena/box2d/blob/master/DynamicsB2Contact.go".
*
* The invocation-chain involves "Step -> SolveTOI -> B2ContactUpdate -> [BeginContact, EndContact, PreSolve]".
*/
pR.CollidableWorld.SetContactListener(listener)
}
type RoomBattleContactListener struct { type RoomBattleContactListener struct {
name string name string
room *Room room *Room
@ -1376,15 +1086,15 @@ type RoomBattleContactListener struct {
// Implementing the GolangBox2d contact listeners [begins]. // Implementing the GolangBox2d contact listeners [begins].
/** /**
* Note that the execution of these listeners is within the SAME GOROUTINE as that of "`battleMainLoop` in the same room". * Note that the execution of these listeners is within the SAME GOROUTINE as that of "`battleMainLoop` in the same room".
* See the comments in `Room.refreshContactListener()` for details. * See the comments in `Room.refreshColliders()` for details.
*/ */
func (l RoomBattleContactListener) BeginContact(contact box2d.B2ContactInterface) { func (l RoomBattleContactListener) BeginContact(contact box2d.B2ContactInterface) {
var pTower *GuardTower var pBarrier *Barrier
var pPlayer *Player var pPlayer *Player
switch v := contact.GetNodeA().Other.GetUserData().(type) { switch v := contact.GetNodeA().Other.GetUserData().(type) {
case *GuardTower: case *Barrier:
pTower = v pBarrier = v
case *Player: case *Player:
pPlayer = v pPlayer = v
default: default:
@ -1392,40 +1102,41 @@ func (l RoomBattleContactListener) BeginContact(contact box2d.B2ContactInterface
} }
switch v := contact.GetNodeB().Other.GetUserData().(type) { switch v := contact.GetNodeB().Other.GetUserData().(type) {
case *GuardTower: case *Barrier:
pTower = v pBarrier = v
case *Player: case *Player:
pPlayer = v pPlayer = v
default: default:
} }
if pTower != nil && pPlayer != nil { if pBarrier != nil && pPlayer != nil {
pTower.InRangePlayers.AppendPlayer(pPlayer) Logger.Info("player begins collision with barrier:", zap.Any("barrier", pBarrier), zap.Any("player", pPlayer))
// TODO: Push back player
} }
} }
func (l RoomBattleContactListener) EndContact(contact box2d.B2ContactInterface) { func (l RoomBattleContactListener) EndContact(contact box2d.B2ContactInterface) {
var pTower *GuardTower var pBarrier *Barrier
var pPlayer *Player var pPlayer *Player
switch v := contact.GetNodeA().Other.GetUserData().(type) { switch v := contact.GetNodeA().Other.GetUserData().(type) {
case *GuardTower: case *Barrier:
pTower = v pBarrier = v
case *Player: case *Player:
pPlayer = v pPlayer = v
default: default:
} }
switch v := contact.GetNodeB().Other.GetUserData().(type) { switch v := contact.GetNodeB().Other.GetUserData().(type) {
case *GuardTower: case *Barrier:
pTower = v pBarrier = v
case *Player: case *Player:
pPlayer = v pPlayer = v
default: default:
} }
if pTower != nil && pPlayer != nil { if pBarrier != nil && pPlayer != nil {
pTower.InRangePlayers.RemovePlayerById(pPlayer.Id) Logger.Info("player ends collision with barrier:", zap.Any("barrier", pBarrier), zap.Any("player", pPlayer))
} }
} }

View File

@ -111,15 +111,10 @@ func InitRoomHeapManager() {
//BattleDurationNanos: int64(5 * 1000 * 1000 * 1000), //BattleDurationNanos: int64(5 * 1000 * 1000 * 1000),
BattleDurationNanos: int64(30 * 1000 * 1000 * 1000), BattleDurationNanos: int64(30 * 1000 * 1000 * 1000),
ServerFPS: 60, ServerFPS: 60,
Treasures: make(map[int32]*Treasure),
Traps: make(map[int32]*Trap),
GuardTowers: make(map[int32]*GuardTower),
Bullets: make(map[int32]*Bullet),
SpeedShoes: make(map[int32]*SpeedShoe),
Barriers: make(map[int32]*Barrier), Barriers: make(map[int32]*Barrier),
Pumpkins: make(map[int32]*Pumpkin),
AccumulatedLocalIdForBullets: 0, AccumulatedLocalIdForBullets: 0,
AllPlayerInputsBuffer: NewRingBuffer(1024), AllPlayerInputsBuffer: NewRingBuffer(1024),
RenderFrameBuffer: NewRingBuffer(1024),
LastAllConfirmedInputFrameId: -1, LastAllConfirmedInputFrameId: -1,
LastAllConfirmedInputFrameIdWithChange: -1, LastAllConfirmedInputFrameIdWithChange: -1,
LastAllConfirmedInputList: make([]uint64, roomCapacity), LastAllConfirmedInputList: make([]uint64, roomCapacity),

View File

@ -1,17 +0,0 @@
package models
import (
"github.com/ByteArena/box2d"
)
type SpeedShoe struct {
Id int32 `json:"id,omitempty"`
LocalIdInBattle int32 `json:"localIdInBattle,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Removed bool `json:"removed,omitempty"`
Type int32 `json:"type,omitempty"`
PickupBoundary *Polygon2D `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
RemovedAtFrameId int32 `json:"-"`
}

View File

@ -19,7 +19,6 @@ import (
const ( const (
LOW_SCORE_TREASURE_TYPE = 1 LOW_SCORE_TREASURE_TYPE = 1
HIGH_SCORE_TREASURE_TYPE = 2 HIGH_SCORE_TREASURE_TYPE = 2
SPEED_SHOES_TYPE = 3 SPEED_SHOES_TYPE = 3
LOW_SCORE_TREASURE_SCORE = 100 LOW_SCORE_TREASURE_SCORE = 100
@ -297,7 +296,7 @@ func DeserializeTsxToColliderDict(pTmxMapIns *TmxMap, byteArrOfTsxFile []byte, f
for _, tile := range pTsxIns.Tiles { for _, tile := range pTsxIns.Tiles {
globalGid := (firstGid + int(tile.Id)) globalGid := (firstGid + int(tile.Id))
/** /**
Per tile xml str could be A tile xml string could be
``` ```
<tile id="13"> <tile id="13">
@ -367,8 +366,6 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMapInB2World map[i
- "Polygon2D"s of "toRetStrToPolygon2DListMap" - "Polygon2D"s of "toRetStrToPolygon2DListMap"
are already transformed into the "coordinate of B2World". are already transformed into the "coordinate of B2World".
-- YFLu
*/ */
for _, objGroup := range pTmxMapIns.ObjectGroups { for _, objGroup := range pTmxMapIns.ObjectGroups {
@ -391,13 +388,8 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMapInB2World map[i
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos) thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
*pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld) *pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld)
} }
case "Pumpkin", "SpeedShoe":
case "Barrier": case "Barrier":
/* // Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]" w.r.t. B2World.
Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is located exactly in an overlapping with "Polygon2D.Points[0]" w.r.t. B2World.
-- YFLu
*/
var pThePolygon2DListToCache *Polygon2DList var pThePolygon2DListToCache *Polygon2DList
_, ok := toRetStrToPolygon2DListMap[objGroup.Name] _, ok := toRetStrToPolygon2DListMap[objGroup.Name]
if false == ok { if false == ok {
@ -422,65 +414,6 @@ func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMapInB2World map[i
} }
*pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld) *pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld)
} }
case "LowScoreTreasure", "GuardTower", "HighScoreTreasure":
/*
Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" ISN'T located exactly in an overlapping with "Polygon2D.Points[0]" w.r.t. B2World, refer to "https://shimo.im/docs/SmLJJhXm2C8XMzZT" for details.
-- YFLu
*/
for _, singleObjInTmxFile := range objGroup.Objects {
if nil == singleObjInTmxFile.Gid {
continue
}
theGlobalGid := singleObjInTmxFile.Gid
theStrToPolygon2DListMap, ok := gidBoundariesMapInB2World[*theGlobalGid]
if false == ok {
continue
}
pThePolygon2DList, ok := theStrToPolygon2DListMap[objGroup.Name]
if false == ok {
continue
}
var pThePolygon2DListToCache *Polygon2DList
_, ok = toRetStrToPolygon2DListMap[objGroup.Name]
if false == ok {
thePolygon2DListToCache := make(Polygon2DList, 0)
toRetStrToPolygon2DListMap[objGroup.Name] = &thePolygon2DListToCache
pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name]
} else {
pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name]
}
for _, thePolygon2D := range *pThePolygon2DList {
theUntransformedBottomCenterAsAnchor := &Vec2D{
X: singleObjInTmxFile.X,
Y: singleObjInTmxFile.Y,
}
theTransformedBottomCenterAsAnchor := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedBottomCenterAsAnchor)
thePolygon2DInWorld := &Polygon2D{
Anchor: &theTransformedBottomCenterAsAnchor,
Points: make([]*Vec2D, len(thePolygon2D.Points)),
TileWidth: thePolygon2D.TileWidth,
TileHeight: thePolygon2D.TileHeight,
}
if nil != singleObjInTmxFile.Width && nil != singleObjInTmxFile.Height {
thePolygon2DInWorld.TmxObjectWidth = *singleObjInTmxFile.Width
thePolygon2DInWorld.TmxObjectHeight = *singleObjInTmxFile.Height
}
for kk, p := range thePolygon2D.Points {
// [WARNING] It's intentionally recreating a copy of "Vec2D" here.
thePolygon2DInWorld.Points[kk] = &Vec2D{
X: p.X,
Y: p.Y,
}
}
*pThePolygon2DListToCache = append(*pThePolygon2DListToCache, thePolygon2DInWorld)
}
}
default: default:
} }
} }

View File

@ -1,39 +0,0 @@
package models
import (
"github.com/ByteArena/box2d"
)
type Trap struct {
Id int32 `json:"id,omitempty"`
LocalIdInBattle int32 `json:"localIdInBattle,omitempty"`
Type int32 `json:"type,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Removed bool `json:"removed,omitempty"`
PickupBoundary *Polygon2D `json:"-"`
TrapBullets []*Bullet `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
RemovedAtFrameId int32 `json:"-"`
}
type GuardTower struct {
Id int32 `json:"id,omitempty"`
LocalIdInBattle int32 `json:"localIdInBattle,omitempty"`
Type int32 `json:"type,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Removed bool `json:"removed,omitempty"`
PickupBoundary *Polygon2D `json:"-"`
TrapBullets []*Bullet `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
RemovedAtFrameId int32 `json:"-"`
InRangePlayers *InRangePlayerCollection `json:"-"`
LastAttackTick int64 `json:"-"`
TileWidth float64 `json:"-"`
TileHeight float64 `json:"-"`
WidthInB2World float64 `json:"-"`
HeightInB2World float64 `json:"-"`
}

View File

@ -1,18 +0,0 @@
package models
import (
"github.com/ByteArena/box2d"
)
type Treasure struct {
Id int32 `json:"id,omitempty"`
LocalIdInBattle int32 `json:"localIdInBattle,omitempty"`
Score int32 `json:"score,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Removed bool `json:"removed,omitempty"`
Type int32 `json:"type,omitempty"`
PickupBoundary *Polygon2D `json:"-"`
CollidableBody *box2d.B2Body `json:"-"`
}

File diff suppressed because it is too large Load Diff

View File

@ -61,73 +61,13 @@ message PlayerMeta {
int32 joinIndex = 5; int32 joinIndex = 5;
} }
message Treasure {
int32 id = 1;
int32 localIdInBattle = 2;
int32 score = 3;
double x = 4;
double y = 5;
bool removed = 6;
int32 type = 7;
}
message Bullet {
int32 localIdInBattle = 1;
double linearSpeed = 2;
double x = 3;
double y = 4;
bool removed = 5;
Vec2D startAtPoint = 6;
Vec2D endAtPoint = 7;
}
message Trap {
int32 id = 1;
int32 localIdInBattle = 2;
int32 type = 3;
double x = 4;
double y = 5;
bool removed = 6;
}
message SpeedShoe {
int32 id = 1;
int32 localIdInBattle = 2;
double x = 3;
double y = 4;
bool removed = 5;
int32 type = 6;
}
message Pumpkin {
int32 localIdInBattle = 1;
double linearSpeed = 2;
double x = 3;
double y = 4;
bool removed = 5;
}
message GuardTower {
int32 id = 1;
int32 localIdInBattle = 2;
int32 type = 3;
double x = 4;
double y = 5;
bool removed = 6;
}
message RoomDownsyncFrame { message RoomDownsyncFrame {
int32 id = 1; int32 id = 1;
int32 refFrameId = 2; int32 refFrameId = 2;
map<int32, Player> players = 3; map<int32, Player> players = 3;
int64 sentAt = 4; int64 sentAt = 4;
int64 countdownNanos = 5; int64 countdownNanos = 5;
map<int32, Treasure> treasures = 6; map<int32, PlayerMeta> playerMetas = 6;
map<int32, Trap> traps = 7;
map<int32, Bullet> bullets = 8;
map<int32, SpeedShoe> speedShoes = 9;
map<int32, GuardTower> guardTowers = 10;
map<int32, PlayerMeta> playerMetas = 11;
} }
message InputFrameUpsync { message InputFrameUpsync {