-
Notifications
You must be signed in to change notification settings - Fork 103
Expand file tree
/
Copy pathCustom.lua
More file actions
259 lines (215 loc) · 9.06 KB
/
Custom.lua
File metadata and controls
259 lines (215 loc) · 9.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
---
-- @Liquipedia
-- page=Module:MatchGroup/Util/Custom
--
-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
--
local Lua = require('Module:Lua')
local Array = Lua.import('Module:Array')
local Faction = Lua.import('Module:Faction')
local FnUtil = Lua.import('Module:FnUtil')
local Logic = Lua.import('Module:Logic')
local Operator = Lua.import('Module:Operator')
local String = Lua.import('Module:StringUtils')
local Table = Lua.import('Module:Table')
local TypeUtil = Lua.import('Module:TypeUtil')
local MatchGroupUtil = Lua.import('Module:MatchGroup/Util')
local Opponent = Lua.import('Module:Opponent/Custom')
local SCORE_STATUS = 'S'
local CustomMatchGroupUtil = Table.deepCopy(MatchGroupUtil)
CustomMatchGroupUtil.types.Faction = TypeUtil.literalUnion(unpack(Faction.getFactions()))
CustomMatchGroupUtil.types.Player = TypeUtil.extendStruct(MatchGroupUtil.types.Player, {
position = 'number?',
faction = CustomMatchGroupUtil.types.Faction,
random = 'boolean',
})
---@class StormgateMatchGroupUtilGamePlayer: standardPlayer
---@field matchplayerIndex integer
---@field heroes string[]?
---@field position integer
---@field random boolean
---@class StormgateMatchGroupUtilGameOpponent: GameOpponent
---@field placement number?
---@field players StormgateMatchGroupUtilGamePlayer[]
---@field score number?
CustomMatchGroupUtil.types.GameOpponent = TypeUtil.struct({
placement = 'number?',
players = TypeUtil.array(CustomMatchGroupUtil.types.Player),
score = 'number?',
})
---@class StormgateMatchGroupUtilGame: MatchGroupUtilGame
---@field opponents StormgateMatchGroupUtilGameOpponent[]
---@field offFactions table<integer, string[]>?
---@class StormgateMatchGroupUtilVeto
---@field by number?
---@field map string
---@class StormgateMatchGroupUtilSubmatch: MatchGroupUtilSubgroup
---@field games StormgateMatchGroupUtilGame[]
---@field mode string
---@field opponents StormgateMatchGroupUtilGameOpponent[]
---@field status string?
---@field winner number?
---@field header string?
---@class StormgateMatchGroupUtilMatch: MatchGroupUtilMatch
---@field games StormgateMatchGroupUtilGame[]
---@field vetoes StormgateMatchGroupUtilVeto[]
---@field submatches StormgateMatchGroupUtilSubmatch[]?
---@field casters string?
---@field isUniformMode boolean
---@param record table
---@return StormgateMatchGroupUtilMatch
function CustomMatchGroupUtil.matchFromRecord(record)
local match = MatchGroupUtil.matchFromRecord(record) --[[@as StormgateMatchGroupUtilMatch]]
-- Add additional fields to opponents
CustomMatchGroupUtil.populateOpponents(match)
-- Adjust game.opponents by looking up game.opponents.players in match.opponents
Array.forEach(match.games, function(game)
game.opponents = CustomMatchGroupUtil.computeGameOpponents(game, match.opponents)
end)
match.isUniformMode = Array.all(match.opponents, function(opponent) return opponent.type ~= Opponent.team end)
local extradata = match.extradata
---@cast extradata table
if not match.isUniformMode then
-- Compute submatches
match.submatches = Array.map(
MatchGroupUtil.groupBySubgroup(match.games),
FnUtil.curry(CustomMatchGroupUtil.constructSubmatch, match)
)
end
-- Add vetoes
match.vetoes = {}
for vetoIndex = 1, math.huge do
local map = Table.extract(extradata, 'veto' .. vetoIndex)
local by = tonumber(Table.extract(extradata, 'veto' .. vetoIndex .. 'by'))
if not map then break end
table.insert(match.vetoes, {map = map, by = by})
end
return match
end
---Move additional fields from extradata to struct
---@param match StormgateMatchGroupUtilMatch
function CustomMatchGroupUtil.populateOpponents(match)
local opponents = match.opponents
for _, opponent in ipairs(opponents) do
opponent.placement2 = tonumber(Table.extract(opponent.extradata, 'placement2'))
opponent.score2 = tonumber(Table.extract(opponent.extradata, 'score2'))
opponent.status2 = opponent.score2 and SCORE_STATUS or nil
for _, player in ipairs(opponent.players) do
player.faction = Table.extract(player.extradata, 'faction') or Faction.defaultFaction
end
end
if #opponents == 2 and opponents[1].score2 and opponents[2].score2 then
local scoreDiff = opponents[1].score2 - opponents[2].score2
opponents[1].placement2 = scoreDiff > 0 and 1 or 2
opponents[2].placement2 = scoreDiff < 0 and 1 or 2
end
end
---@param game StormgateMatchGroupUtilGame
---@param matchOpponents standardOpponent[]
---@return StormgateMatchGroupUtilGameOpponent[]
function CustomMatchGroupUtil.computeGameOpponents(game, matchOpponents)
return Array.map(game.opponents, function(mapOpponent, opponentIndex)
local players = Array.map(mapOpponent.players, function(player, playerIndex)
if Logic.isEmpty(player) then return end
local matchPlayer = (matchOpponents[opponentIndex].players or {})[playerIndex] or {}
return Table.merge({displayName = 'TBD'}, matchPlayer, {
faction = player.faction,
position = tonumber(player.position),
heroes = player.heroes,
random = player.random,
matchPlayerIndex = playerIndex,
})
end)
return Table.merge(mapOpponent, {players = players})
end)
end
---Constructs a submatch object whose properties are aggregated from that of its games.
---@param match StormgateMatchGroupUtilMatch
---@param subgroup MatchGroupUtilSubgroup
---@return StormgateMatchGroupUtilSubmatch
function CustomMatchGroupUtil.constructSubmatch(match, subgroup)
local games = subgroup.games
local firstGame = games[1]
local opponents = Table.deepCopy(firstGame.opponents)
local isSubmatch = String.startsWith(firstGame.map or '', 'Submatch')
if isSubmatch then
games = {firstGame}
end
---@param opponent table
---@param opponentIndex integer
local getOpponentScoreAndStatus = function(opponent, opponentIndex)
local statuses = Array.unique(Array.map(games, function(game)
return game.opponents[opponentIndex].status
end))
opponent.status = #statuses == 1 and statuses[1] ~= SCORE_STATUS and statuses[1] or SCORE_STATUS
opponent.score = isSubmatch and opponent.score or Array.reduce(Array.map(games, function(game)
return (game.winner == opponentIndex and 1 or 0)
end), Operator.add)
end
Array.forEach(opponents, getOpponentScoreAndStatus)
local allPlayed = Array.all(games, function (game)
return game.winner ~= nil or game.status == 'notplayed'
end)
-- can not import this at the top due to loop imports
local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util')
local winner = allPlayed and MatchGroupInputUtil.getWinner('', nil, opponents) or nil
Array.forEach(opponents, function(opponent, opponentIndex)
opponent.placement = MatchGroupInputUtil.placementFromWinner('', winner, opponentIndex)
end)
--check the faction of the players
Array.forEach(opponents, function(_, opponentIndex)
CustomMatchGroupUtil._determineSubmatchPlayerFactions(match, games, opponents, opponentIndex)
end)
return Table.mergeInto({
mode = firstGame.mode,
opponents = opponents,
winner = winner,
header = Table.extract(match.extradata or {}, 'subgroup' .. subgroup.subgroup .. 'header'),
}, subgroup)
end
---@param match StormgateMatchGroupUtilMatch
---@param games StormgateMatchGroupUtilGame[]
---@param opponents StormgateMatchGroupUtilGameOpponent[]
---@param opponentIndex integer
function CustomMatchGroupUtil._determineSubmatchPlayerFactions(match, games, opponents, opponentIndex)
local opponent = opponents[opponentIndex]
local playerFactions = {}
Array.forEach(games, function(game)
for playerIndex, player in pairs(game.opponents[opponentIndex].players) do
playerFactions[playerIndex] = playerFactions[playerIndex] or {}
playerFactions[playerIndex][player.faction] = true
end
end)
local toFaction = function(playerIndex, player)
local isRandom = Array.any(games, function(game)
return game.opponents[opponentIndex].players[playerIndex].random
end)
if isRandom then return Faction.read('r') end
local faction = Table.uniqueKey(playerFactions[playerIndex])
if faction then return faction end
if Table.isNotEmpty(playerFactions[playerIndex]) then
return Faction.read('m')
end
local matchPlayer = match.opponents[opponentIndex].players[player.matchplayerIndex]
return matchPlayer and matchPlayer.faction or Faction.defaultFaction
end
for playerIndex, player in pairs(opponent.players) do
player.faction = toFaction(playerIndex, player)
end
end
---Determines if any players in an opponent aren't playing their main faction by comparing them to a reference opponent.
---Returns the factions played if at least one player chose an offFaction or nil if otherwise.
---@param gameOpponent StormgateMatchGroupUtilGameOpponent
---@param referenceOpponent standardOpponent|StormgateMatchGroupUtilGameOpponent
---@return string[]?
function CustomMatchGroupUtil.computeOffFactions(gameOpponent, referenceOpponent)
local gameFactions = {}
local hasOffFaction = false
for playerIndex, gamePlayer in ipairs(gameOpponent.players) do
local referencePlayer = referenceOpponent.players[playerIndex]
table.insert(gameFactions, gamePlayer.faction)
hasOffFaction = hasOffFaction or gamePlayer.faction ~= referencePlayer.faction
end
return hasOffFaction and gameFactions or nil
end
return CustomMatchGroupUtil