Initial commit.

This commit is contained in:
genxium
2022-09-20 23:50:01 +08:00
commit e90a335c56
432 changed files with 101884 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package models
import (
"github.com/ByteArena/box2d"
)
type Barrier struct {
X float64
Y float64
Type uint32
Boundary *Polygon2D
CollidableBody *box2d.B2Body
}

View File

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,81 @@
package models
import (
"database/sql"
. "server/common"
"server/storage"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
func exist(t string, cond sq.Eq) (bool, error) {
c, err := getCount(t, cond)
if err != nil {
return false, err
}
return c >= 1, nil
}
func getCount(t string, cond sq.Eq) (int, error) {
query, args, err := sq.Select("count(1)").From(t).Where(cond).ToSql()
if err != nil {
return 0, err
}
//Logger.Debug("getCount", zap.String("sql", query), zap.Any("args", args))
var c int
err = storage.MySQLManagerIns.Get(&c, query, args...)
return c, err
}
func insert(t string, cols []string, vs []interface{}) (sql.Result, error) {
query, args, err := sq.Insert(t).Columns(cols...).Values(vs...).ToSql()
Logger.Debug("txInsert", zap.String("sql", query))
if err != nil {
return nil, err
}
result, err := storage.MySQLManagerIns.Exec(query, args...)
return result, err
}
func txInsert(tx *sqlx.Tx, t string, cols []string, vs []interface{}) (sql.Result, error) {
query, args, err := sq.Insert(t).Columns(cols...).Values(vs...).ToSql()
//Logger.Debug("txInsert", zap.String("sql", query))
if err != nil {
return nil, err
}
result, err := tx.Exec(query, args...)
return result, err
}
func getFields(t string, fields []string, cond sq.Eq, dest interface{}) error {
query, args, err := sq.Select(fields...).From(t).Where(cond).Limit(1).ToSql()
Logger.Debug("getFields", zap.String("sql", query), zap.Any("args", args))
if err != nil {
return err
}
err = storage.MySQLManagerIns.Get(dest, query, args...)
return err
}
func getObj(t string, cond sq.Eq, dest interface{}) error {
query, args, err := sq.Select("*").From(t).Where(cond).Limit(1).ToSql()
Logger.Debug("getObj", zap.String("sql", query), zap.Any("args", args))
if err != nil {
return err
}
err = storage.MySQLManagerIns.Get(dest, query, args...)
return err
}
func getList(t string, cond sq.Eq, dest interface{}) error {
query, args, err := sq.Select("*").From(t).Where(cond).ToSql()
Logger.Debug("getList", zap.String("sql", query), zap.Any("args", args))
if err != nil {
return err
}
err = storage.MySQLManagerIns.Select(dest, query, args...)
//Logger.Debug("getList", zap.Error(err))
return err
}

View File

@@ -0,0 +1,164 @@
package models
import (
"fmt"
. "github.com/logrusorgru/aurora"
)
type InRangePlayerCollection struct {
MaxSize int `json:"-"`
CurrentSize int `json:"-"`
CurrentNodePointer *InRangePlayerNode `json:"-"`
InRangePlayerMap map[int32]*InRangePlayerNode `json:"-"`
}
func (p *InRangePlayerCollection) Init(maxSize int) *InRangePlayerCollection {
p = &InRangePlayerCollection{
MaxSize: maxSize,
CurrentSize: 0,
CurrentNodePointer: nil,
InRangePlayerMap: make(map[int32]*InRangePlayerNode),
}
return p
}
func (p *InRangePlayerCollection) Print() {
fmt.Println(Cyan(fmt.Sprintf("{ \n MaxSize: %d, \n CurrentSize: %d, \n }", p.MaxSize, p.CurrentSize)))
}
func (p *InRangePlayerCollection) AppendPlayer(player *Player) *InRangePlayerNode {
if nil != p.InRangePlayerMap[player.Id] { //如果该玩家已存在返回nil
return nil
} else {
//p.CurrentSize
size := p.CurrentSize + 1
if size > p.MaxSize { //超出守护塔的承载范围
fmt.Println(Red(fmt.Sprintf("Error: InRangePlayerCollection overflow, MaxSize: %d, NowSize: %d", p.MaxSize, size)))
return nil
}
p.CurrentSize = size
node := InRangePlayerNode{
Prev: nil,
Next: nil,
player: player,
}
p.InRangePlayerMap[player.Id] = &node
{ //p.CurrentNodePointer
if p.CurrentNodePointer == nil { //刚init好的情况
p.CurrentNodePointer = &node
} else { //加到最后面相当于循环链表prepend
p.CurrentNodePointer.PrependNode(&node)
}
}
return &node
}
}
func (p *InRangePlayerCollection) RemovePlayerById(playerId int32) {
nodePointer := p.InRangePlayerMap[playerId]
delete(p.InRangePlayerMap, playerId)
{ //p.CurrentNodePointer
if p.CurrentNodePointer == nodePointer { //如果正准备攻击这个玩家,将指针移动到Next
p.CurrentNodePointer = nodePointer.Next
}
}
//Remove from the linked list
nodePointer.RemoveFromLink()
p.CurrentSize = p.CurrentSize - 1
}
func (p *InRangePlayerCollection) NextPlayerToAttack() *InRangePlayerNode {
if p.CurrentNodePointer.Next != nil {
p.CurrentNodePointer = p.CurrentNodePointer.Next
} else {
//继续攻击当前玩家
}
return p.CurrentNodePointer
}
//TODO: 完成重构
/// Doubly circular linked list Implement
type InRangePlayerNode struct {
Prev *InRangePlayerNode
Next *InRangePlayerNode
player *Player
}
func (node *InRangePlayerNode) AppendNode(newNode *InRangePlayerNode) *InRangePlayerNode {
if node == nil {
return newNode
} else if node.Next == nil && node.Prev == nil {
node.Prev = newNode
node.Next = newNode
newNode.Prev = node
newNode.Next = node
return node
} else {
oldNext := node.Next
node.Next = newNode
newNode.Next = oldNext
oldNext.Prev = newNode
newNode.Prev = node
return node
}
}
func (node *InRangePlayerNode) PrependNode(newNode *InRangePlayerNode) *InRangePlayerNode {
if node == nil { //没有节点的情况
return newNode
} else if node.Next == nil && node.Prev == nil { //单个节点的情况
node.Prev = newNode
node.Next = newNode
newNode.Prev = node
newNode.Next = node
return node
} else {
oldPrev := node.Prev
node.Prev = newNode
newNode.Prev = oldPrev
oldPrev.Next = newNode
newNode.Next = node
return node
}
}
func (node *InRangePlayerNode) RemoveFromLink() {
if node == nil {
return
} else if node.Next == nil && node.Prev == nil {
node = nil //Wait for GC
} else {
prev := node.Prev
next := node.Next
prev.Next = next
next.Prev = prev
node = nil
}
}
func (node *InRangePlayerNode) Print() {
if node == nil {
fmt.Println("No player in range")
} else if node.Next == nil && node.Prev == nil {
fmt.Println(Red(node.player.Id))
} else {
now := node.Next
fmt.Printf("%d ", Red(node.player.Id))
for node != now {
fmt.Printf("%d ", Green(now.player.Id))
now = now.Next
}
fmt.Println("")
}
}
/// End Doubly circular linked list Implement

132
battle_srv/models/math.go Normal file
View File

@@ -0,0 +1,132 @@
package models
import (
"fmt"
"github.com/ByteArena/box2d"
"math"
)
// Use type `float64` for json unmarshalling of numbers.
type Direction struct {
Dx int32 `json:"dx,omitempty"`
Dy int32 `json:"dy,omitempty"`
}
type Vec2D struct {
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
}
func CreateVec2DFromB2Vec2(b2V2 box2d.B2Vec2) *Vec2D {
return &Vec2D{
X: b2V2.X,
Y: b2V2.Y,
}
}
func (v2 *Vec2D) ToB2Vec2() box2d.B2Vec2 {
return box2d.MakeB2Vec2(v2.X, v2.Y)
}
type Polygon2D struct {
Anchor *Vec2D `json:"-"` // This "Polygon2D.Anchor" is used to be assigned to "B2BodyDef.Position", which in turn is used as the position of the FIRST POINT of the polygon.
Points []*Vec2D `json:"-"`
/*
When used to represent a "polyline directly drawn in a `Tmx file`", we can initialize both "Anchor" and "Points" simultaneously.
Yet when used to represent a "polyline drawn in a `Tsx file`", we have to first initialize "Points w.r.t. center of the tile-rectangle", and then "Anchor(initially nil) of the tile positioned in the `Tmx file`".
Refer to https://shimo.im/docs/SmLJJhXm2C8XMzZT for more information.
*/
/*
[WARNING] Used to cache "`TileWidth & TileHeight` of a Tsx file" only.
*/
TileWidth int
TileHeight int
/*
[WARNING] Used to cache "`Width & TileHeight` of an object in Tmx file" only.
*/
TmxObjectWidth float64
TmxObjectHeight float64
}
func MoveDynamicBody(body *box2d.B2Body, pToTargetPos *box2d.B2Vec2, inSeconds float64) {
if body.GetType() != box2d.B2BodyType.B2_dynamicBody {
return
}
body.SetTransform(*pToTargetPos, 0.0)
body.SetLinearVelocity(box2d.MakeB2Vec2(0.0, 0.0))
body.SetAngularVelocity(0.0)
}
func PrettyPrintFixture(fix *box2d.B2Fixture) {
fmt.Printf("\t\tfriction:\t%v\n", fix.M_friction)
fmt.Printf("\t\trestitution:\t%v\n", fix.M_restitution)
fmt.Printf("\t\tdensity:\t%v\n", fix.M_density)
fmt.Printf("\t\tisSensor:\t%v\n", fix.M_isSensor)
fmt.Printf("\t\tfilter.categoryBits:\t%d\n", fix.M_filter.CategoryBits)
fmt.Printf("\t\tfilter.maskBits:\t%d\n", fix.M_filter.MaskBits)
fmt.Printf("\t\tfilter.groupIndex:\t%d\n", fix.M_filter.GroupIndex)
switch fix.M_shape.GetType() {
case box2d.B2Shape_Type.E_circle:
{
s := fix.M_shape.(*box2d.B2CircleShape)
fmt.Printf("\t\tb2CircleShape shape: {\n")
fmt.Printf("\t\t\tradius:\t%v\n", s.M_radius)
fmt.Printf("\t\t\toffset:\t%v\n", s.M_p)
fmt.Printf("\t\t}\n")
}
break
case box2d.B2Shape_Type.E_polygon:
{
s := fix.M_shape.(*box2d.B2PolygonShape)
fmt.Printf("\t\tb2PolygonShape shape: {\n")
for i := 0; i < s.M_count; i++ {
fmt.Printf("\t\t\t%v\n", s.M_vertices[i])
}
fmt.Printf("\t\t}\n")
}
break
default:
break
}
}
func PrettyPrintBody(body *box2d.B2Body) {
bodyIndex := body.M_islandIndex
fmt.Printf("{\n")
fmt.Printf("\tHeapRAM addr:\t%p\n", body)
fmt.Printf("\ttype:\t%d\n", body.M_type)
fmt.Printf("\tposition:\t%v\n", body.GetPosition())
fmt.Printf("\tangle:\t%v\n", body.M_sweep.A)
fmt.Printf("\tlinearVelocity:\t%v\n", body.GetLinearVelocity())
fmt.Printf("\tangularVelocity:\t%v\n", body.GetAngularVelocity())
fmt.Printf("\tlinearDamping:\t%v\n", body.M_linearDamping)
fmt.Printf("\tangularDamping:\t%v\n", body.M_angularDamping)
fmt.Printf("\tallowSleep:\t%d\n", body.M_flags&box2d.B2Body_Flags.E_autoSleepFlag)
fmt.Printf("\tawake:\t%d\n", body.M_flags&box2d.B2Body_Flags.E_awakeFlag)
fmt.Printf("\tfixedRotation:\t%d\n", body.M_flags&box2d.B2Body_Flags.E_fixedRotationFlag)
fmt.Printf("\tbullet:\t%d\n", body.M_flags&box2d.B2Body_Flags.E_bulletFlag)
fmt.Printf("\tactive:\t%d\n", body.M_flags&box2d.B2Body_Flags.E_activeFlag)
fmt.Printf("\tgravityScale:\t%v\n", body.M_gravityScale)
fmt.Printf("\tislandIndex:\t%v\n", bodyIndex)
fmt.Printf("\tfixtures: {\n")
for f := body.M_fixtureList; f != nil; f = f.M_next {
PrettyPrintFixture(f)
}
fmt.Printf("\t}\n")
fmt.Printf("}\n")
}
func Distance(pt1 *Vec2D, pt2 *Vec2D) float64 {
dx := pt1.X - pt2.X
dy := pt1.Y - pt2.Y
return math.Sqrt(dx*dx + dy*dy)
}

View File

@@ -0,0 +1,203 @@
package models
import (
pb "server/pb_output"
)
func toPbVec2D(modelInstance *Vec2D) *pb.Vec2D {
toRet := &pb.Vec2D{
X: modelInstance.X,
Y: modelInstance.Y,
}
return toRet
}
func toPbPolygon2D(modelInstance *Polygon2D) *pb.Polygon2D {
toRet := &pb.Polygon2D{
Anchor: toPbVec2D(modelInstance.Anchor),
Points: make([]*pb.Vec2D, len(modelInstance.Points)),
}
for index, p := range modelInstance.Points {
toRet.Points[index] = toPbVec2D(p)
}
return toRet
}
func toPbVec2DList(modelInstance *Vec2DList) *pb.Vec2DList {
toRet := &pb.Vec2DList{
Vec2DList: make([]*pb.Vec2D, len(*modelInstance)),
}
for k, v := range *modelInstance {
toRet.Vec2DList[k] = toPbVec2D(v)
}
return toRet
}
func toPbPolygon2DList(modelInstance *Polygon2DList) *pb.Polygon2DList {
toRet := &pb.Polygon2DList{
Polygon2DList: make([]*pb.Polygon2D, len(*modelInstance)),
}
for k, v := range *modelInstance {
toRet.Polygon2DList[k] = toPbPolygon2D(v)
}
return toRet
}
func ToPbStrToBattleColliderInfo(intervalToPing int32, willKickIfInactiveFor int32, boundRoomId int32, stageName string, modelInstance1 StrToVec2DListMap, modelInstance2 StrToPolygon2DListMap, stageDiscreteW int32, stageDiscreteH int32, stageTileW int32, stageTileH int32) *pb.BattleColliderInfo {
toRet := &pb.BattleColliderInfo{
IntervalToPing: intervalToPing,
WillKickIfInactiveFor: willKickIfInactiveFor,
BoundRoomId: boundRoomId,
StageName: stageName,
StrToVec2DListMap: make(map[string]*pb.Vec2DList, 0),
StrToPolygon2DListMap: make(map[string]*pb.Polygon2DList, 0),
StageDiscreteW: stageDiscreteW,
StageDiscreteH: stageDiscreteH,
StageTileW: stageTileW,
StageTileH: stageTileH,
}
for k, v := range modelInstance1 {
toRet.StrToVec2DListMap[k] = toPbVec2DList(v)
}
for k, v := range modelInstance2 {
toRet.StrToPolygon2DListMap[k] = toPbPolygon2DList(v)
}
return toRet
}
func toPbPlayers(modelInstances map[int32]*Player) map[int32]*pb.Player {
toRet := make(map[int32]*pb.Player, 0)
if nil == modelInstances {
return toRet
}
for k, last := range modelInstances {
toRet[k] = &pb.Player{
Id: last.Id,
X: last.X,
Y: last.Y,
Dir: &pb.Direction{
Dx: last.Dir.Dx,
Dy: last.Dir.Dy,
},
Speed: last.Speed,
BattleState: last.BattleState,
Score: last.Score,
Removed: last.Removed,
JoinIndex: last.JoinIndex,
}
}
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
}

142
battle_srv/models/player.go Normal file
View File

@@ -0,0 +1,142 @@
package models
import (
"database/sql"
"fmt"
"github.com/ByteArena/box2d"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
)
type PlayerBattleState struct {
ADDED_PENDING_BATTLE_COLLIDER_ACK int32
READDED_PENDING_BATTLE_COLLIDER_ACK int32
ACTIVE int32
DISCONNECTED int32
LOST int32
EXPELLED_DURING_GAME int32
EXPELLED_IN_DISMISSAL int32
}
var PlayerBattleStateIns PlayerBattleState
func InitPlayerBattleStateIns() {
PlayerBattleStateIns = PlayerBattleState{
ADDED_PENDING_BATTLE_COLLIDER_ACK: 0,
READDED_PENDING_BATTLE_COLLIDER_ACK: 1,
ACTIVE: 2,
DISCONNECTED: 3,
LOST: 4,
EXPELLED_DURING_GAME: 5,
EXPELLED_IN_DISMISSAL: 6,
}
}
type Player struct {
Id int32 `json:"id,omitempty" db:"id"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Dir *Direction `json:"dir,omitempty"`
Speed int32 `json:"speed,omitempty"`
BattleState int32 `json:"battleState,omitempty"`
LastMoveGmtMillis int32 `json:"lastMoveGmtMillis,omitempty"`
Score int32 `json:"score,omitempty"`
Removed bool `json:"removed,omitempty"`
JoinIndex int32
Name string `json:"name,omitempty" db:"name"`
DisplayName string `json:"displayName,omitempty" db:"display_name"`
Avatar string `json:"avatar,omitempty"`
FrozenAtGmtMillis int64 `json:"-" db:"-"`
AddSpeedAtGmtMillis int64 `json:"-" db:"-"`
CreatedAt int64 `json:"-" db:"created_at"`
UpdatedAt int64 `json:"-" db:"updated_at"`
DeletedAt NullInt64 `json:"-" db:"deleted_at"`
TutorialStage int `json:"-" db:"tutorial_stage"`
CollidableBody *box2d.B2Body `json:"-"`
AckingFrameId int32 `json:"ackingFrameId"`
AckingInputFrameId int32 `json:"-"`
LastSentInputFrameId int32 `json:"-"`
}
func ExistPlayerByName(name string) (bool, error) {
return exist("player", sq.Eq{"name": name, "deleted_at": nil})
}
func GetPlayerByName(name string) (*Player, error) {
return getPlayer(sq.Eq{"name": name, "deleted_at": nil})
}
func GetPlayerById(id int) (*Player, error) {
return getPlayer(sq.Eq{"id": id, "deleted_at": nil})
}
func getPlayer(cond sq.Eq) (*Player, error) {
var p Player
err := getObj("player", cond, &p)
if err == sql.ErrNoRows {
return nil, nil
}
p.Dir = &Direction{
Dx: 0,
Dy: 0,
}
return &p, nil
}
func (p *Player) Insert(tx *sqlx.Tx) error {
result, err := txInsert(tx, "player", []string{"name", "display_name", "created_at", "updated_at", "avatar"},
[]interface{}{p.Name, p.DisplayName, p.CreatedAt, p.UpdatedAt, p.Avatar})
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
p.Id = int32(id)
return nil
}
func Update(tx *sqlx.Tx, id int32, p *Player) (bool, error) {
query, args, err := sq.Update("player").
Set("display_name", p.DisplayName).
Set("avatar", p.Avatar).
Where(sq.Eq{"id": id}).ToSql()
fmt.Println(query)
if err != nil {
return false, err
}
result, err := tx.Exec(query, args...)
if err != nil {
fmt.Println("ERRRRRRR: ")
fmt.Println(err)
return false, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return false, err
}
return rowsAffected >= 1, nil
}
func UpdatePlayerTutorialStage(tx *sqlx.Tx, id int) (bool, error) {
query, args, err := sq.Update("player").
Set("tutorial_stage", 1).
Where(sq.Eq{"tutorial_stage": 0, "id": id}).ToSql()
if err != nil {
return false, err
}
result, err := tx.Exec(query, args...)
if err != nil {
return false, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return false, err
}
return rowsAffected >= 1, nil
}

View File

@@ -0,0 +1,35 @@
package models
import (
"database/sql"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
)
type PlayerAuthBinding struct {
Channel int `db:"channel"`
CreatedAt int64 `db:"created_at"`
DeletedAt NullInt64 `db:"deleted_at"`
ExtAuthID string `db:"ext_auth_id"`
PlayerID int `db:"player_id"`
UpdatedAt int64 `db:"updated_at"`
}
func (p *PlayerAuthBinding) Insert(tx *sqlx.Tx) error {
_, err := txInsert(tx, "player_auth_binding", []string{"channel", "created_at", "ext_auth_id",
"player_id", "updated_at"},
[]interface{}{p.Channel, p.CreatedAt, p.ExtAuthID, p.PlayerID, p.UpdatedAt})
return err
}
func GetPlayerAuthBinding(channel int, extAuthID string) (*PlayerAuthBinding, error) {
var p PlayerAuthBinding
err := getObj("player_auth_binding",
sq.Eq{"channel": channel, "ext_auth_id": extAuthID, "deleted_at": nil},
&p)
if err == sql.ErrNoRows {
return nil, nil
}
return &p, nil
}

View File

@@ -0,0 +1,110 @@
package models
import (
"database/sql"
. "server/common"
"server/common/utils"
"server/storage"
sq "github.com/Masterminds/squirrel"
)
type PlayerLogin struct {
CreatedAt int64 `db:"created_at"`
DeletedAt NullInt64 `db:"deleted_at"`
DisplayName NullString `db:"display_name"`
Avatar string `db:"avatar"`
FromPublicIP NullString `db:"from_public_ip"`
ID int `db:"id"`
IntAuthToken string `db:"int_auth_token"`
PlayerID int `db:"player_id"`
UpdatedAt int64 `db:"updated_at"`
}
func (p *PlayerLogin) Insert() error {
result, err := insert("player_login", []string{"created_at", "display_name",
"from_public_ip", "int_auth_token", "player_id", "updated_at", "avatar"},
[]interface{}{p.CreatedAt, p.DisplayName, p.FromPublicIP, p.IntAuthToken,
p.PlayerID, p.UpdatedAt, p.Avatar})
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
p.ID = int(id)
return nil
}
func GetPlayerLoginByToken(token string) (*PlayerLogin, error) {
var p PlayerLogin
err := getObj("player_login",
sq.Eq{"int_auth_token": token, "deleted_at": nil},
&p)
if err == sql.ErrNoRows {
return nil, nil
}
return &p, nil
}
func GetPlayerLoginByPlayerId(playerId int) (*PlayerLogin, error) {
var p PlayerLogin
err := getObj("player_login",
sq.Eq{"player_id": playerId, "deleted_at": nil},
&p)
if err == sql.ErrNoRows {
return nil, nil
}
return &p, nil
}
func GetPlayerIdByToken(token string) (int, error) {
var p PlayerLogin
err := getFields("player_login", []string{"player_id"},
sq.Eq{"int_auth_token": token, "deleted_at": nil},
&p)
if err == sql.ErrNoRows {
return 0, nil
}
return p.PlayerID, nil
}
// TODO 封装到helper
func DelPlayerLoginByToken(token string) error {
query, args, err := sq.Update("player_login").Set("deleted_at", utils.UnixtimeMilli()).
Where(sq.Eq{"int_auth_token": token}).ToSql()
if err != nil {
return err
}
//Logger.Debug(query, args)
_, err = storage.MySQLManagerIns.Exec(query, args...)
if err == sql.ErrNoRows {
return nil
}
if err != nil {
return err
}
return nil
}
func EnsuredPlayerLoginByToken(id int, token string) (bool, error) {
return exist("player_login", sq.Eq{"int_auth_token": token, "deleted_at": nil, "player_id": id})
}
func EnsuredPlayerLoginById(id int) (bool, error) {
return exist("player_login", sq.Eq{"player_id": id, "deleted_at": nil})
}
func CleanExpiredPlayerLoginToken() error {
now := utils.UnixtimeMilli()
max := now - int64(Constants.Player.IntAuthTokenTTLSeconds*1000)
query, args, err := sq.Update("player_login").Set("deleted_at", now).
Where(sq.LtOrEq{"created_at": max}).ToSql()
if err != nil {
return err
}
_, err = storage.MySQLManagerIns.Exec(query, args...)
return err
}

View File

@@ -0,0 +1,129 @@
package models
import (
"database/sql"
"errors"
. "server/common"
"server/common/utils"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type PlayerWallet struct {
CreatedAt int64 `json:"-" db:"created_at"`
DeletedAt NullInt64 `json:"-" db:"deleted_at"`
Gem int `json:"gem" db:"gem"`
ID int `json:"-" db:"id"`
UpdatedAt int64 `json:"-" db:"updated_at"`
}
func (p *PlayerWallet) Insert(tx *sqlx.Tx) error {
_, err := txInsert(tx, "player_wallet", []string{"id", "created_at", "updated_at"},
[]interface{}{p.ID, p.CreatedAt, p.UpdatedAt})
return err
}
func GetPlayerWalletById(id int) (*PlayerWallet, error) {
var p PlayerWallet
err := getObj("player_wallet", sq.Eq{"id": id, "deleted_at": nil}, &p)
if err == sql.ErrNoRows {
return nil, nil
}
return &p, nil
}
func CostPlayerWallet(tx *sqlx.Tx, id int, currency int, val int) (int, error) {
var column string
switch currency {
case Constants.Player.Diamond:
column = "diamond"
case Constants.Player.Energy:
column = "energy"
case Constants.Player.Gold:
column = "gold"
}
if column == "" {
Logger.Debug("CostPlayerWallet Error Currency",
zap.Int("currency", currency), zap.Int("val", val))
return Constants.RetCode.MysqlError, errors.New("error currency")
}
now := utils.UnixtimeMilli()
query, args, err := sq.Update("player_wallet").
Set(column, sq.Expr(column+"-?", val)).Set("updated_at", now).
Where(sq.Eq{"id": id, "deleted_at": nil}).
Where(sq.GtOrEq{column: val}).ToSql()
Logger.Debug("CostPlayerWallet", zap.String("sql", query), zap.Any("args", args))
if err != nil {
return Constants.RetCode.MysqlError, err
}
result, err := tx.Exec(query, args...)
if err != nil {
return Constants.RetCode.MysqlError, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return Constants.RetCode.MysqlError, err
}
ok := rowsAffected >= 1
Logger.Debug("CostPlayerWallet", zap.Int64("rowsAffected", rowsAffected),
zap.Bool("cost", ok))
if !ok {
var ret int
switch currency {
case Constants.Player.Diamond:
ret = Constants.RetCode.LackOfDiamond
case Constants.Player.Energy:
ret = Constants.RetCode.LackOfEnergy
case Constants.Player.Gold:
ret = Constants.RetCode.LackOfGold
}
return ret, nil
}
return 0, nil
}
func AddPlayerWallet(tx *sqlx.Tx, id int, currency int, val int) (int, error) {
var column string
switch currency {
case Constants.Player.Diamond:
column = "diamond"
case Constants.Player.Energy:
column = "energy"
case Constants.Player.Gold:
column = "gold"
}
if column == "" {
Logger.Debug("CostPlayerWallet Error Currency",
zap.Int("currency", currency), zap.Int("val", val))
return Constants.RetCode.MysqlError, errors.New("error currency")
}
now := utils.UnixtimeMilli()
query, args, err := sq.Update("player_wallet").
Set(column, sq.Expr(column+"+?", val)).Set("updated_at", now).
Where(sq.Eq{"id": id, "deleted_at": nil}).ToSql()
Logger.Debug("AddPlayerWallet", zap.String("sql", query), zap.Any("args", args))
if err != nil {
return Constants.RetCode.MysqlError, err
}
result, err := tx.Exec(query, args...)
if err != nil {
return Constants.RetCode.MysqlError, err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return Constants.RetCode.MysqlError, err
}
ok := rowsAffected >= 1
Logger.Debug("AddPlayerWallet", zap.Int64("rowsAffected", rowsAffected),
zap.Bool("add", ok))
if !ok {
return Constants.RetCode.UnknownError, nil
}
return 0, nil
}

View File

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,73 @@
package models
type RingBuffer struct {
Ed int32 // write index
St int32 // read index
EdFrameId int32
StFrameId int32
N int32
Cnt int32 // the count of valid elements in the buffer, used mainly to distinguish what "st == ed" means for "Pop" and "Get" methods
Eles []interface{}
}
func NewRingBuffer(n int32) *RingBuffer {
return &RingBuffer{
Ed: 0,
St: 0,
N: n,
Cnt: 0,
Eles: make([]interface{}, n),
}
}
func (rb *RingBuffer) Put(pItem interface{}) {
rb.Eles[rb.Ed] = pItem
rb.EdFrameId++
rb.Cnt++
rb.Ed++
if rb.Ed >= rb.N {
rb.Ed -= rb.N // Deliberately not using "%" operator for performance concern
}
}
func (rb *RingBuffer) Pop() interface{} {
if 0 == rb.Cnt {
return nil
}
pItem := rb.Eles[rb.St]
rb.StFrameId++
rb.Cnt--
rb.St++
if rb.St >= rb.N {
rb.St -= rb.N
}
return pItem
}
func (rb *RingBuffer) GetByOffset(offsetFromSt int32) interface{} {
if 0 == rb.Cnt {
return nil
}
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]
}
} else {
// if rb.St >= rb.Ed
// case#2: 0...ed...st...N-1
if arrIdx >= rb.N {
arrIdx -= rb.N
}
if arrIdx >= rb.St || arrIdx < rb.Ed {
return rb.Eles[arrIdx]
}
}
return nil
}
func (rb *RingBuffer) GetByFrameId(frameId int32) interface{} {
return rb.GetByOffset(frameId - rb.StFrameId)
}

1450
battle_srv/models/room.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
package models
import (
"container/heap"
"fmt"
"github.com/gorilla/websocket"
"go.uber.org/zap"
. "server/common"
"sync"
)
// Reference https://github.com/genxium/GoStructPrac.
type RoomHeap []*Room
type RoomMap map[int32]*Room
var (
// NOTE: For the package exported instances of non-primitive types to be accessed as singletons, they must be of pointer types.
RoomHeapMux *sync.Mutex
RoomHeapManagerIns *RoomHeap
RoomMapManagerIns *RoomMap
)
func (pPq *RoomHeap) PrintInOrder() {
pq := *pPq
fmt.Printf("The RoomHeap instance now contains:\n")
for i := 0; i < len(pq); i++ {
fmt.Printf("{index: %d, roomID: %d, score: %.2f} ", i, pq[i].Id, pq[i].Score)
}
fmt.Printf("\n")
}
func (pq RoomHeap) Len() int { return len(pq) }
func (pq RoomHeap) Less(i, j int) bool {
return pq[i].Score > pq[j].Score
}
func (pq *RoomHeap) Swap(i, j int) {
(*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i]
(*pq)[i].Index = i
(*pq)[j].Index = j
}
func (pq *RoomHeap) Push(pItem interface{}) {
// NOTE: Must take input param type `*Room` here.
n := len(*pq)
pItem.(*Room).Index = n
*pq = append(*pq, pItem.(*Room))
}
func (pq *RoomHeap) Pop() interface{} {
old := *pq
n := len(old)
if n == 0 {
return nil
}
pItem := old[n-1]
if pItem.Score <= float32(0.0) {
return nil
}
pItem.Index = -1 // for safety
*pq = old[0 : n-1]
// NOTE: Must return instance which is directly castable to type `*Room` here.
return pItem
}
func (pq *RoomHeap) update(pItem *Room, Score float32) {
// NOTE: Must use type `*Room` here.
heap.Fix(pq, pItem.Index)
}
func (pq *RoomHeap) Update(pItem *Room, Score float32) {
pq.update(pItem, Score)
}
func PrintRoomMap() {
fmt.Printf("The RoomMap instance now contains:\n")
for _, pR := range *RoomMapManagerIns {
fmt.Printf("{roomID: %d, score: %.2f} ", pR.Id, pR.Score)
}
fmt.Printf("\n")
}
func InitRoomHeapManager() {
RoomHeapMux = new(sync.Mutex)
// Init "pseudo class constants".
InitRoomBattleStateIns()
InitPlayerBattleStateIns()
initialCountOfRooms := 32
pq := make(RoomHeap, initialCountOfRooms)
roomMap := make(RoomMap, initialCountOfRooms)
for i := 0; i < initialCountOfRooms; i++ {
roomCapacity := 2
joinIndexBooleanArr := make([]bool, roomCapacity)
for index, _ := range joinIndexBooleanArr {
joinIndexBooleanArr[index] = false
}
currentRoomBattleState := RoomBattleStateIns.IDLE
pq[i] = &Room{
Id: int32(i + 1),
Players: make(map[int32]*Player),
PlayerDownsyncSessionDict: make(map[int32]*websocket.Conn),
PlayerSignalToCloseDict: make(map[int32]SignalToCloseConnCbType),
Capacity: roomCapacity,
Score: calRoomScore(0, roomCapacity, currentRoomBattleState),
State: currentRoomBattleState,
Index: i,
Tick: 0,
EffectivePlayerCount: 0,
//BattleDurationNanos: int64(5 * 1000 * 1000 * 1000),
BattleDurationNanos: int64(30 * 1000 * 1000 * 1000),
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),
Pumpkins: make(map[int32]*Pumpkin),
AccumulatedLocalIdForBullets: 0,
AllPlayerInputsBuffer: NewRingBuffer(1024),
LastAllConfirmedInputFrameId: -1,
LastAllConfirmedInputFrameIdWithChange: -1,
LastAllConfirmedInputList: make([]uint64, roomCapacity),
InputDelayFrames: 4,
InputScaleFrames: 2,
JoinIndexBooleanArr: joinIndexBooleanArr,
}
roomMap[pq[i].Id] = pq[i]
pq[i].ChooseStage()
}
heap.Init(&pq)
RoomHeapManagerIns = &pq
RoomMapManagerIns = &roomMap
Logger.Info("The RoomHeapManagerIns has been initialized:", zap.Any("addr", fmt.Sprintf("%p", RoomHeapManagerIns)), zap.Any("size", len(*RoomHeapManagerIns)))
Logger.Info("The RoomMapManagerIns has been initialized:", zap.Any("size", len(*RoomMapManagerIns)))
}

View File

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,537 @@
package models
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
"github.com/ByteArena/box2d"
"go.uber.org/zap"
"io/ioutil"
"math"
. "server/common"
"strconv"
"strings"
)
const (
LOW_SCORE_TREASURE_TYPE = 1
HIGH_SCORE_TREASURE_TYPE = 2
SPEED_SHOES_TYPE = 3
LOW_SCORE_TREASURE_SCORE = 100
HIGH_SCORE_TREASURE_SCORE = 200
FLIPPED_HORIZONTALLY_FLAG uint32 = 0x80000000
FLIPPED_VERTICALLY_FLAG uint32 = 0x40000000
FLIPPED_DIAGONALLY_FLAG uint32 = 0x20000000
)
// For either a "*.tmx" or "*.tsx" file. [begins]
type TmxOrTsxProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
type TmxOrTsxProperties struct {
Property []*TmxOrTsxProperty `xml:"property"`
}
type TmxOrTsxPolyline struct {
Points string `xml:"points,attr"`
}
type TmxOrTsxObject struct {
Id int `xml:"id,attr"`
Gid *int `xml:"gid,attr"`
X float64 `xml:"x,attr"`
Y float64 `xml:"y,attr"`
Properties *TmxOrTsxProperties `xml:"properties"`
Polyline *TmxOrTsxPolyline `xml:"polyline"`
Width *float64 `xml:"width,attr"`
Height *float64 `xml:"height,attr"`
}
type TmxOrTsxObjectGroup struct {
Draworder string `xml:"draworder,attr"`
Name string `xml:"name,attr"`
Objects []*TmxOrTsxObject `xml:"object"`
}
type TmxOrTsxImage struct {
Source string `xml:"source,attr"`
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
}
// For either a "*.tmx" or "*.tsx" file. [ends]
// Within a "*.tsx" file. [begins]
type Tsx struct {
Name string `xml:"name,attr"`
TileWidth int `xml:"tilewidth,attr"`
TileHeight int `xml:"tileheight,attr"`
TileCount int `xml:"tilecount,attr"`
Columns int `xml:"columns,attr"`
Image []*TmxOrTsxImage `xml:"image"`
Tiles []*TsxTile `xml:"tile"`
}
type TsxTile struct {
Id int `xml:"id,attr"`
ObjectGroup *TmxOrTsxObjectGroup `xml:"objectgroup"`
Properties *TmxOrTsxProperties `xml:"properties"`
}
// Within a "*.tsx" file. [ends]
// Within a "*.tmx" file. [begins]
type TmxLayerDecodedTileData struct {
Id uint32
Tileset *TmxTileset
FlipHorizontal bool
FlipVertical bool
FlipDiagonal bool
}
type TmxLayerEncodedData struct {
Encoding string `xml:"encoding,attr"`
Compression string `xml:"compression,attr"`
Value string `xml:",chardata"`
}
type TmxLayer struct {
Name string `xml:"name,attr"`
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
Data *TmxLayerEncodedData `xml:"data"`
Tile []*TmxLayerDecodedTileData
}
type TmxTileset struct {
FirstGid uint32 `xml:"firstgid,attr"`
Name string `xml:"name,attr"`
TileWidth int `xml:"tilewidth,attr"`
TileHeight int `xml:"tileheight,attr"`
Images []*TmxOrTsxImage `xml:"image"`
Source string `xml:"source,attr"`
}
type TmxMap struct {
Version string `xml:"version,attr"`
Orientation string `xml:"orientation,attr"`
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
TileWidth int `xml:"tilewidth,attr"`
TileHeight int `xml:"tileheight,attr"`
Properties []*TmxOrTsxProperties `xml:"properties"`
Tilesets []*TmxTileset `xml:"tileset"`
Layers []*TmxLayer `xml:"layer"`
ObjectGroups []*TmxOrTsxObjectGroup `xml:"objectgroup"`
}
// Within a "*.tmx" file. [ends]
func (d *TmxLayerEncodedData) decodeBase64() ([]byte, error) {
r := bytes.NewReader([]byte(strings.TrimSpace(d.Value)))
decr := base64.NewDecoder(base64.StdEncoding, r)
if d.Compression == "zlib" {
rclose, err := zlib.NewReader(decr)
if err != nil {
Logger.Error("tmx data decode zlib error: ", zap.Any("encoding", d.Encoding), zap.Any("compression", d.Compression), zap.Any("value", d.Value))
return nil, err
}
return ioutil.ReadAll(rclose)
}
Logger.Error("tmx data decode invalid compression: ", zap.Any("encoding", d.Encoding), zap.Any("compression", d.Compression), zap.Any("value", d.Value))
return nil, errors.New("Invalid compression.")
}
func (l *TmxLayer) decodeBase64() ([]uint32, error) {
databytes, err := l.Data.decodeBase64()
if err != nil {
return nil, err
}
if l.Width == 0 || l.Height == 0 {
return nil, errors.New("Zero width or height.")
}
if len(databytes) != l.Height*l.Width*4 {
Logger.Error("TmxLayer decodeBase64 invalid data bytes:", zap.Any("width", l.Width), zap.Any("height", l.Height), zap.Any("data lenght", len(databytes)))
return nil, errors.New("Data length error.")
}
dindex := 0
gids := make([]uint32, l.Height*l.Width)
for h := 0; h < l.Height; h++ {
for w := 0; w < l.Width; w++ {
gid := uint32(databytes[dindex]) |
uint32(databytes[dindex+1])<<8 |
uint32(databytes[dindex+2])<<16 |
uint32(databytes[dindex+3])<<24
dindex += 4
gids[h*l.Width+w] = gid
}
}
return gids, nil
}
type Vec2DList []*Vec2D
type Polygon2DList []*Polygon2D
type StrToVec2DListMap map[string]*Vec2DList // Note that it's deliberately NOT using "map[string]Vec2DList", for the easy of passing return value to "models/room.go".
type StrToPolygon2DListMap map[string]*Polygon2DList // Note that it's deliberately NOT using "map[string]Polygon2DList", for the easy of passing return value to "models/room.go".
func TmxPolylineToPolygon2DInB2World(pTmxMapIns *TmxMap, singleObjInTmxFile *TmxOrTsxObject, targetPolyline *TmxOrTsxPolyline) (*Polygon2D, error) {
if nil == targetPolyline {
return nil, nil
}
singleValueArray := strings.Split(targetPolyline.Points, " ")
pointsCount := len(singleValueArray)
if pointsCount >= box2d.B2_maxPolygonVertices {
return nil, errors.New(fmt.Sprintf("During `TmxPolylineToPolygon2DInB2World`, you have a polygon with pointsCount == %v, exceeding or equal to box2d.B2_maxPolygonVertices == %v, of polyines [%v]", pointsCount, box2d.B2_maxPolygonVertices, singleValueArray))
}
theUntransformedAnchor := &Vec2D{
X: singleObjInTmxFile.X,
Y: singleObjInTmxFile.Y,
}
theTransformedAnchor := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedAnchor)
thePolygon2DFromPolyline := &Polygon2D{
Anchor: &theTransformedAnchor,
Points: make([]*Vec2D, len(singleValueArray)),
}
for k, value := range singleValueArray {
thePolygon2DFromPolyline.Points[k] = &Vec2D{}
for kk, v := range strings.Split(value, ",") {
coordinateValue, err := strconv.ParseFloat(v, 64)
if nil != err {
panic(err)
}
if 0 == (kk % 2) {
thePolygon2DFromPolyline.Points[k].X = (coordinateValue)
} else {
thePolygon2DFromPolyline.Points[k].Y = (coordinateValue)
}
}
// Transform to B2World space coordinate.
tmp := &Vec2D{
X: thePolygon2DFromPolyline.Points[k].X,
Y: thePolygon2DFromPolyline.Points[k].Y,
}
transformedTmp := pTmxMapIns.continuousObjLayerVecToContinuousMapNodeVec(tmp)
thePolygon2DFromPolyline.Points[k].X = transformedTmp.X
thePolygon2DFromPolyline.Points[k].Y = transformedTmp.Y
}
return thePolygon2DFromPolyline, nil
}
func TsxPolylineToOffsetsWrtTileCenterInB2World(pTmxMapIns *TmxMap, singleObjInTsxFile *TmxOrTsxObject, targetPolyline *TmxOrTsxPolyline, pTsxIns *Tsx) (*Polygon2D, error) {
if nil == targetPolyline {
return nil, nil
}
var factorHalf float64 = 0.5
offsetFromTopLeftInTileLocalCoordX := singleObjInTsxFile.X
offsetFromTopLeftInTileLocalCoordY := singleObjInTsxFile.Y
singleValueArray := strings.Split(targetPolyline.Points, " ")
pointsCount := len(singleValueArray)
if pointsCount >= box2d.B2_maxPolygonVertices {
return nil, errors.New(fmt.Sprintf("During `TsxPolylineToOffsetsWrtTileCenterInB2World`, you have a polygon with pointsCount == %v, exceeding or equal to box2d.B2_maxPolygonVertices == %v", pointsCount, box2d.B2_maxPolygonVertices))
}
thePolygon2DFromPolyline := &Polygon2D{
Anchor: nil,
Points: make([]*Vec2D, pointsCount),
TileWidth: pTsxIns.TileWidth,
TileHeight: pTsxIns.TileHeight,
}
/*
[WARNING] In this case, the "Treasure"s and "GuardTower"s are put into Tmx file as "ImageObject"s, of each the "ProportionalAnchor" is (0.5, 0). Therefore we calculate that "thePolygon2DFromPolyline.Points" are "offsets(in B2World) w.r.t. the BottomCenter". See https://shimo.im/docs/SmLJJhXm2C8XMzZT for details.
*/
for k, value := range singleValueArray {
thePolygon2DFromPolyline.Points[k] = &Vec2D{}
for kk, v := range strings.Split(value, ",") {
coordinateValue, err := strconv.ParseFloat(v, 64)
if nil != err {
panic(err)
}
if 0 == (kk % 2) {
// W.r.t. center.
thePolygon2DFromPolyline.Points[k].X = (coordinateValue + offsetFromTopLeftInTileLocalCoordX) - factorHalf*float64(pTsxIns.TileWidth)
} else {
// W.r.t. bottom.
thePolygon2DFromPolyline.Points[k].Y = float64(pTsxIns.TileHeight) - (coordinateValue + offsetFromTopLeftInTileLocalCoordY)
}
}
// No need to transform for B2World space coordinate because the marks in a Tsx file is already rectilinear.
}
return thePolygon2DFromPolyline, nil
}
func DeserializeTsxToColliderDict(pTmxMapIns *TmxMap, byteArrOfTsxFile []byte, firstGid int, gidBoundariesMapInB2World map[int]StrToPolygon2DListMap) error {
pTsxIns := &Tsx{}
err := xml.Unmarshal(byteArrOfTsxFile, pTsxIns)
if nil != err {
panic(err)
}
/*
// For debug-printing only. -- YFLu, 2019-09-04.
reserializedTmxMap, err := pTmxMapIns.ToXML()
if nil != err {
panic(err)
}
*/
for _, tile := range pTsxIns.Tiles {
globalGid := (firstGid + int(tile.Id))
/**
Per tile xml str could be
```
<tile id="13">
<objectgroup draworder="index">
<object id="1" x="-154" y="-159">
<properties>
<property name="boundary_type" value="guardTower"/>
</properties>
<polyline points="0,0 -95,179 18,407 361,434 458,168 333,-7"/>
</object>
</objectgroup>
</tile>
```
, we currently REQUIRE that "`an object of a tile` with ONE OR MORE polylines must come with a single corresponding '<property name=`type` value=`...` />', and viceversa".
Refer to https://shimo.im/docs/SmLJJhXm2C8XMzZT for how we theoretically fit a "Polyline in Tsx" into a "Polygon2D" and then into the corresponding "B2BodyDef & B2Body in the `world of colliding bodies`".
*/
theObjGroup := tile.ObjectGroup
if nil == theObjGroup {
continue
}
for _, singleObj := range theObjGroup.Objects {
if nil == singleObj.Polyline {
// Temporarily omit those non-polyline-containing objects.
continue
}
if nil == singleObj.Properties.Property || "boundary_type" != singleObj.Properties.Property[0].Name {
continue
}
key := singleObj.Properties.Property[0].Value
var theStrToPolygon2DListMap StrToPolygon2DListMap
if existingStrToPolygon2DListMap, ok := gidBoundariesMapInB2World[globalGid]; ok {
theStrToPolygon2DListMap = existingStrToPolygon2DListMap
} else {
gidBoundariesMapInB2World[globalGid] = make(StrToPolygon2DListMap, 0)
theStrToPolygon2DListMap = gidBoundariesMapInB2World[globalGid]
}
var pThePolygon2DList *Polygon2DList
if _, ok := theStrToPolygon2DListMap[key]; ok {
pThePolygon2DList = theStrToPolygon2DListMap[key]
} else {
thePolygon2DList := make(Polygon2DList, 0)
theStrToPolygon2DListMap[key] = &thePolygon2DList
pThePolygon2DList = theStrToPolygon2DListMap[key]
}
thePolygon2DFromPolyline, err := TsxPolylineToOffsetsWrtTileCenterInB2World(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns)
if nil != err {
panic(err)
}
*pThePolygon2DList = append(*pThePolygon2DList, thePolygon2DFromPolyline)
}
}
return nil
}
func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMapInB2World map[int]StrToPolygon2DListMap) (int32, int32, int32, int32, StrToVec2DListMap, StrToPolygon2DListMap, error) {
toRetStrToVec2DListMap := make(StrToVec2DListMap, 0)
toRetStrToPolygon2DListMap := make(StrToPolygon2DListMap, 0)
/*
Note that both
- "Vec2D"s of "toRetStrToVec2DListMap", and
- "Polygon2D"s of "toRetStrToPolygon2DListMap"
are already transformed into the "coordinate of B2World".
-- YFLu
*/
for _, objGroup := range pTmxMapIns.ObjectGroups {
switch objGroup.Name {
case "PlayerStartingPos":
var pTheVec2DListToCache *Vec2DList
_, ok := toRetStrToVec2DListMap[objGroup.Name]
if false == ok {
theVec2DListToCache := make(Vec2DList, 0)
toRetStrToVec2DListMap[objGroup.Name] = &theVec2DListToCache
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
} else {
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
}
for _, singleObjInTmxFile := range objGroup.Objects {
theUntransformedPos := &Vec2D{
X: singleObjInTmxFile.X,
Y: singleObjInTmxFile.Y,
}
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
*pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld)
}
case "Pumpkin", "SpeedShoe":
case "Barrier":
/*
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
_, 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 _, singleObjInTmxFile := range objGroup.Objects {
if nil == singleObjInTmxFile.Polyline {
continue
}
if nil == singleObjInTmxFile.Properties.Property || "boundary_type" != singleObjInTmxFile.Properties.Property[0].Name || "barrier" != singleObjInTmxFile.Properties.Property[0].Value {
continue
}
thePolygon2DInWorld, err := TmxPolylineToPolygon2DInB2World(pTmxMapIns, singleObjInTmxFile, singleObjInTmxFile.Polyline)
if nil != err {
panic(err)
}
*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:
}
}
return int32(pTmxMapIns.Width), int32(pTmxMapIns.Height), int32(pTmxMapIns.TileWidth), int32(pTmxMapIns.TileHeight), toRetStrToVec2DListMap, toRetStrToPolygon2DListMap, nil
}
func (pTmxMap *TmxMap) ToXML() (string, error) {
ret, err := xml.Marshal(pTmxMap)
return string(ret[:]), err
}
type TileRectilinearSize struct {
Width float64
Height float64
}
func (pTmxMapIns *TmxMap) continuousObjLayerVecToContinuousMapNodeVec(continuousObjLayerVec *Vec2D) Vec2D {
var tileRectilinearSize TileRectilinearSize
tileRectilinearSize.Width = float64(pTmxMapIns.TileWidth)
tileRectilinearSize.Height = float64(pTmxMapIns.TileHeight)
tileSizeUnifiedLength := math.Sqrt(tileRectilinearSize.Width*tileRectilinearSize.Width*0.25 + tileRectilinearSize.Height*tileRectilinearSize.Height*0.25)
isometricObjectLayerPointOffsetScaleFactor := (tileSizeUnifiedLength / tileRectilinearSize.Height)
cosineThetaRadian := (tileRectilinearSize.Width * 0.5) / tileSizeUnifiedLength
sineThetaRadian := (tileRectilinearSize.Height * 0.5) / tileSizeUnifiedLength
transMat := [...][2]float64{
{isometricObjectLayerPointOffsetScaleFactor * cosineThetaRadian, -isometricObjectLayerPointOffsetScaleFactor * cosineThetaRadian},
{-isometricObjectLayerPointOffsetScaleFactor * sineThetaRadian, -isometricObjectLayerPointOffsetScaleFactor * sineThetaRadian},
}
convertedVecX := transMat[0][0]*continuousObjLayerVec.X + transMat[0][1]*continuousObjLayerVec.Y
convertedVecY := transMat[1][0]*continuousObjLayerVec.X + transMat[1][1]*continuousObjLayerVec.Y
converted := Vec2D{
X: convertedVecX,
Y: convertedVecY,
}
return converted
}
func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continuousObjLayerOffset *Vec2D) Vec2D {
layerOffset := Vec2D{
X: 0,
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
}
calibratedVec := continuousObjLayerOffset
convertedVec := pTmxMapIns.continuousObjLayerVecToContinuousMapNodeVec(calibratedVec)
toRet := Vec2D{
X: layerOffset.X + convertedVec.X,
Y: layerOffset.Y + convertedVec.Y,
}
return toRet
}

39
battle_srv/models/trap.go Normal file
View File

@@ -0,0 +1,39 @@
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

@@ -0,0 +1,18 @@
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:"-"`
}

74
battle_srv/models/type.go Normal file
View File

@@ -0,0 +1,74 @@
package models
import (
"database/sql"
"encoding/json"
)
type NullInt64 struct {
sql.NullInt64
}
func NewNullInt64(s int64) NullInt64 {
ns := NullInt64{}
ns.Int64 = s
ns.Valid = true
return ns
}
func (v NullInt64) MarshalJSON() ([]byte, error) {
if v.Valid {
return json.Marshal(v.Int64)
} else {
return json.Marshal(nil)
}
}
func (v *NullInt64) UnmarshalJSON(data []byte) error {
var s *int64
//Logger.Debugf("%s\n", data)
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s != nil {
v.Valid = true
v.Int64 = *s
} else {
v.Valid = false
}
return nil
}
type NullString struct {
sql.NullString
}
func NewNullString(s string) NullString {
ns := NullString{}
ns.String = s
ns.Valid = true
return ns
}
func (v NullString) MarshalJSON() ([]byte, error) {
if v.Valid {
return json.Marshal(v.String)
} else {
return json.Marshal(nil)
}
}
func (v *NullString) UnmarshalJSON(data []byte) error {
var s *string
//Logger.Debugf("%s\n", data)
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s != nil {
v.Valid = true
v.String = *s
} else {
v.Valid = false
}
return nil
}