2022-10-14 08:08:22 +00:00
package dnmshared
2022-09-20 15:50:01 +00:00
import (
"bytes"
"compress/zlib"
"encoding/base64"
"encoding/xml"
"errors"
"go.uber.org/zap"
"io/ioutil"
"math"
"strconv"
"strings"
2022-11-09 04:19:29 +00:00
. "dnmshared/protos"
2022-09-20 15:50:01 +00:00
)
const (
LOW_SCORE_TREASURE_TYPE = 1
HIGH_SCORE_TREASURE_TYPE = 2
2022-09-29 04:21:04 +00:00
SPEED_SHOES_TYPE = 3
2022-09-20 15:50:01 +00:00
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
}
2022-10-15 13:39:22 +00:00
type StrToVec2DListMap map [ string ] * Vec2DList
type StrToPolygon2DListMap map [ string ] * Polygon2DList
2022-09-20 15:50:01 +00:00
2022-10-01 09:26:37 +00:00
func tmxPolylineToPolygon2D ( pTmxMapIns * TmxMap , singleObjInTmxFile * TmxOrTsxObject , targetPolyline * TmxOrTsxPolyline ) ( * Polygon2D , error ) {
2022-09-20 15:50:01 +00:00
if nil == targetPolyline {
return nil , nil
}
singleValueArray := strings . Split ( targetPolyline . Points , " " )
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 )
}
}
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
}
2022-10-01 09:26:37 +00:00
func tsxPolylineToOffsetsWrtTileCenter ( pTmxMapIns * TmxMap , singleObjInTsxFile * TmxOrTsxObject , targetPolyline * TmxOrTsxPolyline , pTsxIns * Tsx ) ( * Polygon2D , error ) {
2022-09-20 15:50:01 +00:00
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 )
thePolygon2DFromPolyline := & Polygon2D {
Anchor : nil ,
Points : make ( [ ] * Vec2D , pointsCount ) ,
}
/ *
2022-10-01 09:26:37 +00:00
[ 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 the "thePolygon2DFromPolyline.Points" are "offsets w.r.t. the BottomCenter" . See https : //shimo.im/docs/SmLJJhXm2C8XMzZT for details.
2022-09-20 15:50:01 +00:00
* /
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 )
}
}
}
return thePolygon2DFromPolyline , nil
}
2022-10-01 09:26:37 +00:00
func DeserializeTsxToColliderDict ( pTmxMapIns * TmxMap , byteArrOfTsxFile [ ] byte , firstGid int , gidBoundariesMap map [ int ] StrToPolygon2DListMap ) error {
2022-09-20 15:50:01 +00:00
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 ) )
/ * *
2022-09-26 15:09:18 +00:00
A tile xml string could be
2022-09-20 15:50:01 +00:00
` ` `
< 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" .
2022-10-01 09:26:37 +00:00
Refer to https : //shimo.im/docs/SmLJJhXm2C8XMzZT for how we theoretically fit a "Polyline in Tsx" into a "Polygon2D".
2022-09-20 15:50:01 +00:00
* /
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
2022-10-01 09:26:37 +00:00
if existingStrToPolygon2DListMap , ok := gidBoundariesMap [ globalGid ] ; ok {
2022-09-20 15:50:01 +00:00
theStrToPolygon2DListMap = existingStrToPolygon2DListMap
} else {
2022-10-01 09:26:37 +00:00
gidBoundariesMap [ globalGid ] = make ( StrToPolygon2DListMap , 0 )
theStrToPolygon2DListMap = gidBoundariesMap [ globalGid ]
2022-09-20 15:50:01 +00:00
}
var pThePolygon2DList * Polygon2DList
if _ , ok := theStrToPolygon2DListMap [ key ] ; ok {
pThePolygon2DList = theStrToPolygon2DListMap [ key ]
} else {
2022-11-09 04:19:29 +00:00
thePolygon2DListEles := make ( [ ] * Polygon2D , 0 )
2022-09-20 15:50:01 +00:00
theStrToPolygon2DListMap [ key ] = & thePolygon2DList
pThePolygon2DList = theStrToPolygon2DListMap [ key ]
}
2022-10-01 09:26:37 +00:00
thePolygon2DFromPolyline , err := tsxPolylineToOffsetsWrtTileCenter ( pTmxMapIns , singleObj , singleObj . Polyline , pTsxIns )
2022-09-20 15:50:01 +00:00
if nil != err {
panic ( err )
}
* pThePolygon2DList = append ( * pThePolygon2DList , thePolygon2DFromPolyline )
}
}
return nil
}
2022-10-01 09:26:37 +00:00
func ParseTmxLayersAndGroups ( pTmxMapIns * TmxMap , gidBoundariesMap map [ int ] StrToPolygon2DListMap ) ( int32 , int32 , int32 , int32 , StrToVec2DListMap , StrToPolygon2DListMap , error ) {
2022-09-20 15:50:01 +00:00
toRetStrToVec2DListMap := make ( StrToVec2DListMap , 0 )
toRetStrToPolygon2DListMap := make ( StrToPolygon2DListMap , 0 )
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
2022-10-01 12:45:38 +00:00
}
pTheVec2DListToCache = toRetStrToVec2DListMap [ objGroup . Name ]
2022-09-20 15:50:01 +00:00
for _ , singleObjInTmxFile := range objGroup . Objects {
theUntransformedPos := & Vec2D {
X : singleObjInTmxFile . X ,
Y : singleObjInTmxFile . Y ,
}
thePosInWorld := pTmxMapIns . continuousObjLayerOffsetToContinuousMapNodePos ( theUntransformedPos )
* pTheVec2DListToCache = append ( * pTheVec2DListToCache , & thePosInWorld )
}
case "Barrier" :
2022-10-01 09:26:37 +00:00
// Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]".
2022-09-20 15:50:01 +00:00
var pThePolygon2DListToCache * Polygon2DList
_ , ok := toRetStrToPolygon2DListMap [ objGroup . Name ]
if false == ok {
thePolygon2DListToCache := make ( Polygon2DList , 0 )
toRetStrToPolygon2DListMap [ objGroup . Name ] = & thePolygon2DListToCache
}
2022-10-01 12:45:38 +00:00
pThePolygon2DListToCache = toRetStrToPolygon2DListMap [ objGroup . Name ]
2022-09-20 15:50:01 +00:00
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
}
2022-10-01 09:26:37 +00:00
thePolygon2DInWorld , err := tmxPolylineToPolygon2D ( pTmxMapIns , singleObjInTmxFile , singleObjInTmxFile . Polyline )
2022-09-20 15:50:01 +00:00
if nil != err {
panic ( err )
}
* 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
}