mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-25 11:18:55 +00:00
471 lines
15 KiB
Go
471 lines
15 KiB
Go
package dnmshared
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"go.uber.org/zap"
|
|
"io/ioutil"
|
|
. "jsexport/battle"
|
|
"math"
|
|
"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 StrToVec2DListMap map[string](*[]*Vec2D)
|
|
type StrToPolygon2DListMap map[string](*[]*Polygon2D)
|
|
|
|
func tmxPolylineToPolygon2D(pTmxMapIns *TmxMap, singleObjInTmxFile *TmxOrTsxObject, targetPolyline *TmxOrTsxPolyline) (*Polygon2D, error) {
|
|
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
|
|
}
|
|
|
|
func tsxPolylineToOffsetsWrtTileCenter(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)
|
|
|
|
thePolygon2DFromPolyline := &Polygon2D{
|
|
Anchor: nil,
|
|
Points: make([]*Vec2D, pointsCount),
|
|
}
|
|
|
|
/*
|
|
[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.
|
|
*/
|
|
|
|
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
|
|
}
|
|
|
|
func DeserializeTsxToColliderDict(pTmxMapIns *TmxMap, byteArrOfTsxFile []byte, firstGid int, gidBoundariesMap 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))
|
|
/**
|
|
A tile xml string 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".
|
|
*/
|
|
|
|
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 := gidBoundariesMap[globalGid]; ok {
|
|
theStrToPolygon2DListMap = existingStrToPolygon2DListMap
|
|
} else {
|
|
gidBoundariesMap[globalGid] = make(StrToPolygon2DListMap, 0)
|
|
theStrToPolygon2DListMap = gidBoundariesMap[globalGid]
|
|
}
|
|
|
|
var pThePolygon2DList *[]*Polygon2D = nil
|
|
if _, ok := theStrToPolygon2DListMap[key]; !ok {
|
|
tmp := make([]*Polygon2D, 0)
|
|
theStrToPolygon2DListMap[key] = &tmp
|
|
}
|
|
pThePolygon2DList = theStrToPolygon2DListMap[key]
|
|
|
|
thePolygon2DFromPolyline, err := tsxPolylineToOffsetsWrtTileCenter(pTmxMapIns, singleObj, singleObj.Polyline, pTsxIns)
|
|
if nil != err {
|
|
panic(err)
|
|
}
|
|
*pThePolygon2DList = append(*pThePolygon2DList, thePolygon2DFromPolyline)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ParseTmxLayersAndGroups(pTmxMapIns *TmxMap, gidBoundariesMap map[int]StrToPolygon2DListMap) (int32, int32, int32, int32, StrToVec2DListMap, StrToPolygon2DListMap, error) {
|
|
toRetStrToVec2DListMap := make(StrToVec2DListMap, 0)
|
|
toRetStrToPolygon2DListMap := make(StrToPolygon2DListMap, 0)
|
|
|
|
for _, objGroup := range pTmxMapIns.ObjectGroups {
|
|
switch objGroup.Name {
|
|
case "PlayerStartingPos":
|
|
var pTheVec2DListToCache *[]*Vec2D = nil
|
|
if _, ok := toRetStrToVec2DListMap[objGroup.Name]; !ok {
|
|
tmp := make([]*Vec2D, 0)
|
|
toRetStrToVec2DListMap[objGroup.Name] = &tmp
|
|
}
|
|
pTheVec2DListToCache = toRetStrToVec2DListMap[objGroup.Name]
|
|
for _, singleObjInTmxFile := range objGroup.Objects {
|
|
theUntransformedPos := &Vec2D{
|
|
X: singleObjInTmxFile.X,
|
|
Y: singleObjInTmxFile.Y,
|
|
}
|
|
thePosInWorld := pTmxMapIns.continuousObjLayerOffsetToContinuousMapNodePos(theUntransformedPos)
|
|
*pTheVec2DListToCache = append(*pTheVec2DListToCache, &thePosInWorld)
|
|
}
|
|
case "Barrier":
|
|
// Note that in this case, the "Polygon2D.Anchor" of each "TmxOrTsxObject" is exactly overlapping with "Polygon2D.Points[0]".
|
|
var pThePolygon2DListToCache *[]*Polygon2D = nil
|
|
if _, ok := toRetStrToPolygon2DListMap[objGroup.Name]; !ok {
|
|
tmp := make([]*Polygon2D, 0)
|
|
toRetStrToPolygon2DListMap[objGroup.Name] = &tmp
|
|
}
|
|
pThePolygon2DListToCache = toRetStrToPolygon2DListMap[objGroup.Name]
|
|
|
|
for _, singleObjInTmxFile := range objGroup.Objects {
|
|
if nil == singleObjInTmxFile.Polyline {
|
|
pts := make([]*Vec2D, 4)
|
|
s := make([]string, 0)
|
|
pts[0] = &Vec2D{
|
|
X: float64(0),
|
|
Y: float64(0),
|
|
}
|
|
pts[1] = &Vec2D{
|
|
X: float64(0),
|
|
Y: singleObjInTmxFile.Height,
|
|
}
|
|
pts[2] = &Vec2D{
|
|
X: singleObjInTmxFile.Width,
|
|
Y: singleObjInTmxFile.Height,
|
|
}
|
|
pts[3] = &Vec2D{
|
|
X: singleObjInTmxFile.Width,
|
|
Y: float64(0),
|
|
}
|
|
for _, pt := range pts {
|
|
s = append(s, fmt.Sprintf("%v,%v", pt.X, pt.Y))
|
|
}
|
|
singleObjInTmxFile.Polyline = &TmxOrTsxPolyline{
|
|
Points: strings.Join(s, " "),
|
|
}
|
|
}
|
|
|
|
thePolygon2DInWorld, err := tmxPolylineToPolygon2D(pTmxMapIns, singleObjInTmxFile, singleObjInTmxFile.Polyline)
|
|
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 {
|
|
if "orthogonal" == pTmxMapIns.Orientation {
|
|
return Vec2D{
|
|
X: continuousObjLayerVec.X,
|
|
Y: -continuousObjLayerVec.Y,
|
|
}
|
|
}
|
|
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 {
|
|
var layerOffset Vec2D
|
|
if "orthogonal" == pTmxMapIns.Orientation {
|
|
layerOffset = Vec2D{
|
|
X: -float64(pTmxMapIns.Width*pTmxMapIns.TileWidth) * 0.5,
|
|
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
|
|
}
|
|
} else {
|
|
// "isometric" == pTmxMapIns.Orientation
|
|
layerOffset = Vec2D{
|
|
X: 0,
|
|
Y: float64(pTmxMapIns.Height*pTmxMapIns.TileHeight) * 0.5,
|
|
}
|
|
}
|
|
|
|
convertedVec := pTmxMapIns.continuousObjLayerVecToContinuousMapNodeVec(continuousObjLayerOffset)
|
|
|
|
return Vec2D{
|
|
X: layerOffset.X + convertedVec.X,
|
|
Y: layerOffset.Y + convertedVec.Y,
|
|
}
|
|
}
|