From 49dacdba1f7ea3539460d8779144c4d2aaa45695 Mon Sep 17 00:00:00 2001 From: Andras Belicza Date: Thu, 9 Jun 2022 13:29:37 +0200 Subject: [PATCH] Parse PlayerOwners and PlayerSides from map data --- cmd/screp/screp.go | 2 +- rep/mapdata.go | 16 ++++++++ rep/repcore/enums.go | 86 ++++++++++++++++++++++++++++++++++++++++++ repparser/repparser.go | 24 +++++++++++- 4 files changed, 126 insertions(+), 2 deletions(-) diff --git a/cmd/screp/screp.go b/cmd/screp/screp.go index c2ec812..b6503b3 100644 --- a/cmd/screp/screp.go +++ b/cmd/screp/screp.go @@ -26,7 +26,7 @@ import ( const ( appName = "screp" - appVersion = "v1.7.1" + appVersion = "v1.7.2" appAuthor = "Andras Belicza" appHome = "https://github.com/icza/screp" ) diff --git a/rep/mapdata.go b/rep/mapdata.go index 1861dc2..f7969a5 100644 --- a/rep/mapdata.go +++ b/rep/mapdata.go @@ -24,6 +24,12 @@ type MapData struct { // Scenario description Description string + // PlayerOwners defines the player types (player owners). + PlayerOwners []*repcore.PlayerOwner + + // PlayerSides defines the player sides (player races). + PlayerSides []*repcore.PlayerSide + // Tiles is the tile data of the map (within the tile set): width x height elements. // 1 Tile is 32 units (pixel) Tiles []uint16 @@ -41,6 +47,16 @@ type MapData struct { Debug *MapDataDebug `json:"-"` } +// MaxHumanPlayers returns the max number of human players on the map. +func (md *MapData) MaxHumanPlayers() (count int) { + for _, owner := range md.PlayerOwners { + if owner == repcore.PlayerOwnerHumanOpenSlot { + count++ + } + } + return +} + // Resource describes a resource (mineral field of vespene geyser). type Resource struct { // Location of the resource diff --git a/rep/repcore/enums.go b/rep/repcore/enums.go index 2424a44..1d41e4f 100644 --- a/rep/repcore/enums.go +++ b/rep/repcore/enums.go @@ -389,3 +389,89 @@ func TileSetByID(ID uint16) *TileSet { } return &TileSet{UnknownEnum(ID), ID} } + +// PlayerOwner describes a player owner. +type PlayerOwner struct { + Enum + + // ID as it appears in replays + ID uint8 +} + +// PlayerOwners is an enumeration of the possible player owners +var PlayerOwners = []*PlayerOwner{ + {Enum{"Inactive"}, 0x00}, + {Enum{"Computer (game)"}, 0x01}, + {Enum{"Occupied by Human Player"}, 0x02}, + {Enum{"Rescue Passive"}, 0x03}, + {Enum{"Unused"}, 0x04}, + {Enum{"Computer"}, 0x05}, + {Enum{"Human (Open Slot)"}, 0x06}, + {Enum{"Neutral"}, 0x07}, + {Enum{"Closed slot"}, 0x08}, +} + +// Named player owners +var ( + PlayerOwnerInactive = PlayerOwners[0] + PlayerOwnerComputerGame = PlayerOwners[1] + PlayerOwnerOccupiedByHumanPlayer = PlayerOwners[2] + PlayerOwnerRescuePassive = PlayerOwners[3] + PlayerOwnerUnused = PlayerOwners[4] + PlayerOwnerComputer = PlayerOwners[5] + PlayerOwnerHumanOpenSlot = PlayerOwners[6] + PlayerOwnerNeutral = PlayerOwners[7] + PlayerOwnerClosedSlot = PlayerOwners[8] +) + +// PlayerOwnerByID returns the PlayerOwner for a given ID. +// A new PlayerOwner with Unknown name is returned if one is not found +// for the given ID (preserving the unknown ID). +func PlayerOwnerByID(ID uint8) *PlayerOwner { + if int(ID) < len(PlayerOwners) { + return PlayerOwners[ID] + } + return &PlayerOwner{UnknownEnum(ID), ID} +} + +// PlayerSide describes a player side (race). +type PlayerSide struct { + Enum + + // ID as it appears in replays + ID uint8 +} + +// PlayerSides is an enumeration of the possible player sides +var PlayerSides = []*PlayerSide{ + {Enum{"Zerg"}, 0x00}, + {Enum{"Terran"}, 0x01}, + {Enum{"Protoss"}, 0x02}, + {Enum{"Invalid (Independent)"}, 0x03}, + {Enum{"Invalid (Neutral)"}, 0x04}, + {Enum{"User Selectable"}, 0x05}, + {Enum{"Random (Forced)"}, 0x06}, // Acts as a selected race + {Enum{"Inactive"}, 0x07}, +} + +// Named player sides +var ( + PlayerSideZerg = PlayerSides[0] + PlayerSideTerran = PlayerSides[1] + PlayerSideProtoss = PlayerSides[2] + PlayerSideInvalidIndependent = PlayerSides[3] + PlayerSideInvalidNeutral = PlayerSides[4] + PlayerSideUserSelectable = PlayerSides[5] + PlayerSideRandomForced = PlayerSides[6] + PlayerSideInactive = PlayerSides[7] +) + +// PlayerSideByID returns the PlayerSide for a given ID. +// A new PlayerSide with Unknown name is returned if one is not found +// for the given ID (preserving the unknown ID). +func PlayerSideByID(ID uint8) *PlayerSide { + if int(ID) < len(PlayerSides) { + return PlayerSides[ID] + } + return &PlayerSide{UnknownEnum(ID), ID} +} diff --git a/repparser/repparser.go b/repparser/repparser.go index d5fe868..c473a57 100644 --- a/repparser/repparser.go +++ b/repparser/repparser.go @@ -31,6 +31,8 @@ https://github.com/neivv/jssuh Map Data format: +https://www.starcraftai.com/wiki/CHK_Format + http://www.staredit.net/wiki/index.php/Scenario.chk http://blog.naver.com/PostView.nhn?blogId=wisdomswrap&logNo=60119755717&parentCategoryNo=&categoryNo=19&viewDate=&isShowPopularPosts=false&from=postView @@ -59,7 +61,7 @@ import ( const ( // Version is a Semver2 compatible version of the parser. - Version = "v1.8.1" + Version = "v1.8.2" ) var ( @@ -735,6 +737,26 @@ func parseMapData(data []byte, r *rep.Replay, cfg Config) error { r.Header.MapHeight = height } } + case "OWNR": // StarCraft Player Types + count := uint32(12) // 12 bytes, 1 for each player + if count > ssSize { + count = ssSize + } + owners := sr.readSlice(count) + md.PlayerOwners = make([]*repcore.PlayerOwner, len(owners)) + for i, id := range owners { + md.PlayerOwners[i] = repcore.PlayerOwnerByID(id) + } + case "SIDE": // Player races + count := uint32(12) // 12 bytes, 1 for each player + if count > ssSize { + count = ssSize + } + sides := sr.readSlice(count) + md.PlayerSides = make([]*repcore.PlayerSide, len(sides)) + for i, id := range sides { + md.PlayerSides[i] = repcore.PlayerSideByID(id) + } case "MTXM": // Tile sub-section // map_width*map_height (a tile is an uint16 value) maxI := ssSize / 2