Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(match2): clash royale support #4321

Merged
merged 35 commits into from
Sep 19, 2024
Merged
Changes from 28 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
231ed81
Adding MVPs to Clash Royale. Also adding misisng
FenrirWulf Jun 7, 2024
0ec0374
Minor Linter fixes, and renaming file
FenrirWulf Jun 7, 2024
8dea5c8
MGIC linter fixes and minor refactors
FenrirWulf Jun 7, 2024
7d6172c
EOF empty line, since VSC doesnt seem to want to push it
FenrirWulf Jun 7, 2024
aac21e4
FIxing typo
FenrirWulf Jun 9, 2024
14cf71f
Merge branch 'CR-M2-MVPs' of https://github.com/Liquipedia/Lua-Module…
FenrirWulf Jun 9, 2024
490bc98
Code Cleanup
FenrirWulf Jun 11, 2024
1668045
Using commons version
FenrirWulf Jun 14, 2024
3b87147
Adding annotations
FenrirWulf Jun 14, 2024
c9b6fe6
Trailing whitespace
FenrirWulf Jun 14, 2024
8842e6b
Converting to array
FenrirWulf Jun 30, 2024
90a27a8
Minor rewrites for Summary
FenrirWulf Jul 4, 2024
702199d
Batch changes
FenrirWulf Jul 4, 2024
f250ec5
Casters & Array stuff
FenrirWulf Jul 4, 2024
05bb294
Removing MVPs for new PR
FenrirWulf Jul 13, 2024
2fe1538
Adjusting for invalid card input
FenrirWulf Jul 16, 2024
36c6fff
Code cleanup
FenrirWulf Jul 17, 2024
ef2b8aa
minor code cleanup
FenrirWulf Jul 22, 2024
0354b69
some cleanup
hjpalpha Aug 1, 2024
f438a4b
kick trailing white space
hjpalpha Aug 2, 2024
a2731c7
only allow walkovers via score inputs
hjpalpha Aug 2, 2024
cb464a0
adjust for stupid bs tower cards
hjpalpha Aug 3, 2024
847f767
fix: do the pagify after retrieving players
hjpalpha Aug 5, 2024
94918d3
refactor mgi
hjpalpha Sep 3, 2024
eeca984
kick unused
hjpalpha Sep 3, 2024
aa094a4
Merge branch 'main' into CR-M2-MVPs
hjpalpha Sep 4, 2024
7c1503a
fix
hjpalpha Sep 4, 2024
b210691
pass correct var to `mapIsFinished`
hjpalpha Sep 4, 2024
dc6aa50
Update components/match2/wikis/clashroyale/match_group_input_custom.lua
hjpalpha Sep 9, 2024
1cf460d
Merge branch 'main' into CR-M2-MVPs
hjpalpha Sep 10, 2024
2113199
#4650
hjpalpha Sep 10, 2024
fe67332
from review
hjpalpha Sep 18, 2024
cb90d58
Merge branch 'main' into CR-M2-MVPs
hjpalpha Sep 18, 2024
3a5d138
not needed
hjpalpha Sep 18, 2024
31dcf37
Update components/match2/wikis/clashroyale/match_group_input_custom.lua
hjpalpha Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 360 additions & 0 deletions components/match2/wikis/clashroyale/match_group_input_custom.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
---
FenrirWulf marked this conversation as resolved.
Show resolved Hide resolved
-- @Liquipedia
-- wiki=clashroyale
-- page=Module:MatchGroup/Input/Custom
--
-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
--

local Array = require('Module:Array')
local CardNames = mw.loadData('Module:CardNames')
local FnUtil = require('Module:FnUtil')
local Json = require('Module:Json')
local Logic = require('Module:Logic')
local Lua = require('Module:Lua')
local Operator = require('Module:Operator')
local Table = require('Module:Table')
local Variables = require('Module:Variables')

local MatchGroupInputUtil = Lua.import('Module:MatchGroup/Input/Util')
local OpponentLibraries = require('Module:OpponentLibraries')
local Opponent = OpponentLibraries.Opponent
local Streams = Lua.import('Module:Links/Stream')

local DEFAULT_MODE = 'solo'
local OPPONENT_CONFIG = {
resolveRedirect = true,
pagifyTeamNames = true,
pagifyPlayerNames = true,
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved
}
local ROYALE_API_PREFIX = 'https://royaleapi.com/'

local CustomMatchGroupInput = {}
local MatchFunctions = {}
local MapFunctions = {}

---@param match table
---@param options table?
---@return table
function CustomMatchGroupInput.processMatch(match, options)
Table.mergeInto(match, MatchGroupInputUtil.readDate(match.date))

local opponents = Array.mapIndexes(function(opponentIndex)
return MatchGroupInputUtil.readOpponent(match, opponentIndex, OPPONENT_CONFIG)
end)

local games = MatchFunctions.extractMaps(match, opponents)

local autoScoreFunction = MatchGroupInputUtil.canUseAutoScore(match, games)
and MatchFunctions.calculateMatchScore(games, opponents)
or nil

Array.forEach(opponents, function(opponent, opponentIndex)
opponent.score, opponent.status = MatchGroupInputUtil.computeOpponentScore({
walkover = match.walkover,
winner = match.winner,
opponentIndex = opponentIndex,
score = opponent.score,
}, autoScoreFunction)
end)

match.bestof = MatchFunctions.getBestOf(match.bestof)

local winnerInput = match.winner --[[@as string?]]
local finishedInput = match.finished --[[@as string?]]
match.finished = MatchGroupInputUtil.matchIsFinished(match, opponents)

if match.finished then
match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents)
match.walkover = MatchGroupInputUtil.getWalkover(match.resulttype, opponents)
match.winner = MatchGroupInputUtil.getWinner(match.resulttype, winnerInput, opponents)
MatchGroupInputUtil.setPlacement(opponents, match.winner, 1, 2)
elseif MatchGroupInputUtil.isNotPlayed(winnerInput, finishedInput) then
match.resulttype = MatchGroupInputUtil.getResultType(winnerInput, finishedInput, opponents)
match.winner = nil
end

MatchFunctions.getTournamentVars(match)

match.stream = Streams.processStreams(match)
match.links = MatchFunctions.getLinks(match)
match.vod = Logic.nilIfEmpty(match.vod)
match.extradata = MatchFunctions.getExtraData(match, #games)

match.games = games
match.opponents = opponents

return match
end

---@param match table
---@return table
function MatchFunctions.getLinks(match)
return {
royaleapi = match.royaleapi and (ROYALE_API_PREFIX .. match.royaleapi) or nil,
}
FenrirWulf marked this conversation as resolved.
Show resolved Hide resolved
end

---@param match table
---@return table
function MatchFunctions.getTournamentVars(match)
match.mode = Logic.emptyOr(match.mode, Variables.varDefault('tournament_mode', DEFAULT_MODE))
return MatchGroupInputUtil.getCommonTournamentVars(match)
end

---@param match table
---@param opponents table[]
---@return table[]
function MatchFunctions.extractMaps(match, opponents)
local maps = {}
local subGroup = 0
for mapKey, mapInput, mapIndex in Table.iter.pairsByPrefix(match, 'map', {requireIndex = true}) do
local map
map, subGroup = MapFunctions.readMap(mapInput, mapIndex, subGroup, #opponents)

map.participants = MapFunctions.getParticipants(mapInput, opponents)
map.extradata = MapFunctions.getExtraData(mapInput, map.participants)

map.vod = Logic.emptyOr(mapInput.vod, match['vodgame' .. mapIndex])

table.insert(maps, map)
match[mapKey] = nil
end

return maps
end

---@param maps table[]
---@param opponents table[]
---@return fun(opponentIndex: integer): integer?
function MatchFunctions.calculateMatchScore(maps, opponents)
return function(opponentIndex)
local calculatedScore = MatchGroupInputUtil.computeMatchScoreFromMapWinners(maps, opponentIndex)
if not calculatedScore then return end
local opponent = opponents[opponentIndex]
return calculatedScore + (opponent.extradata.advantage or 0) - (opponent.extradata.penalty or 0)
end
end

---@param bestofInput string|integer?
---@return integer?
function MatchFunctions.getBestOf(bestofInput)
local bestof = tonumber(bestofInput) or tonumber(Variables.varDefault('match_bestof'))

if bestof then
Variables.varDefine('match_bestof', bestof)
end

return bestof
end

---@param match table
---@param numberOfGames integer
---@return table
function MatchFunctions.getExtraData(match, numberOfGames)
local extradata = {
t1bans = MatchFunctions.readBans(match.t1bans),
t2bans = MatchFunctions.readBans(match.t2bans),
mvp = MatchGroupInputUtil.readMvp(match),
}

local prefix = 'subgroup%d+'
Table.mergeInto(extradata, Table.filterByKey(match, function(key) return key:match(prefix .. 'header') end))
Table.mergeInto(extradata, Table.filterByKey(match, function(key) return key:match(prefix .. 'iskoth') end))

local t1Bans = Table.filterByKey(match, function(key) return key:match(prefix .. 't1bans') end)
Table.mergeInto(extradata, Table.mapValues(t1Bans, MatchFunctions.readBans))

local t2Bans = Table.filterByKey(match, function(key) return key:match(prefix .. 't2bans') end)
Table.mergeInto(extradata, Table.mapValues(t2Bans, MatchFunctions.readBans))

return extradata
end

---@param bansInput string
---@return table
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved
function MatchFunctions.readBans(bansInput)
local bans = CustomMatchGroupInput._readCards(bansInput)

return Logic.nilIfEmpty(bans)
end

---@param mapInput table
---@param mapIndex integer
---@param subGroup integer
---@param opponentCount integer
---@return table
---@return integer
function MapFunctions.readMap(mapInput, mapIndex, subGroup, opponentCount)
subGroup = tonumber(mapInput.subgroup) or (subGroup + 1)

local map = {
map = 'Set ' .. mapIndex, -- CR has no map names, use generic one instead
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved
subgroup = subGroup,
}

map.finished = MatchGroupInputUtil.mapIsFinished(mapInput)
local opponentInfo = Array.map(Array.range(1, opponentCount), function(opponentIndex)
local score, status = MatchGroupInputUtil.computeOpponentScore({
walkover = mapInput.walkover,
winner = mapInput.winner,
opponentIndex = opponentIndex,
score = mapInput['score' .. opponentIndex],
}, MapFunctions.calculateMapScore(mapInput, map.finished))
return {score = score, status = status}
end)

map.scores = Array.map(opponentInfo, Operator.property('score'))

if map.finished or MatchGroupInputUtil.isNotPlayed(map.winner, mapInput.finished) then
map.resulttype = MatchGroupInputUtil.getResultType(mapInput.winner, mapInput.finished, opponentInfo)
map.walkover = MatchGroupInputUtil.getWalkover(map.resulttype, opponentInfo)
map.winner = MatchGroupInputUtil.getWinner(map.resulttype, mapInput.winner, opponentInfo)
end

return map, subGroup
end

---@param mapInput table
---@param finished boolean
---@return fun(opponentIndex: integer): integer?
function MapFunctions.calculateMapScore(mapInput, finished)
local winner = tonumber(mapInput.winner)
return function(opponentIndex)
local manualScore = tonumber(mapInput['score' .. opponentIndex])
if manualScore then return
manualScore
end
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved

-- TODO Better to check if map has started, rather than finished, for a more correct handling
if not winner and not finished then
return
end

return winner == opponentIndex and 1 or 0
end
end

---@param mapInput table
---@param opponents table[]
---@return table<string, {player: string, played: boolean, cards: table}>
function MapFunctions.getParticipants(mapInput, opponents)
local participants = {}
Array.forEach(opponents, function(opponent, opponentIndex)
if opponent.type == Opponent.literal then
return
elseif opponent.type == Opponent.team then
Table.mergeInto(participants, MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex))
return
end
Table.mergeInto(participants, MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex))
end)

return participants
end

---@param mapInput table
---@param opponent table
---@param opponentIndex integer
---@return table<string, {player: string, played: boolean, cards: table}>
function MapFunctions.getTeamParticipants(mapInput, opponent, opponentIndex)
local players = Array.mapIndexes(function(playerIndex)
return Logic.nilIfEmpty(mapInput['t' .. opponentIndex .. 'p' .. playerIndex])
end)

local participants, unattachedParticipants = MatchGroupInputUtil.parseParticipants(
opponent.match2players,
players,
function(playerIndex)
local prefix = 'o' .. opponentIndex .. 'p' .. playerIndex
return {
name = mapInput[prefix],
link = Logic.nilIfEmpty(mapInput[prefix .. 'link']) or Variables.varDefault(mapInput[prefix] .. '_page'),
cards = CustomMatchGroupInput._readCards(mapInput[prefix .. 'c']),
}
end,
function(playerIndex, playerIdData, playerInputData)
return {
played = true,
player = playerIdData.name or playerInputData.link,
cards = playerInputData.cards,
}
end
)

Array.forEach(unattachedParticipants, function(participant)
table.insert(opponent.match2players, {
name = participant.player,
displayname = participant.player,
})
participants[#opponent.match2players] = participant
end)

return Table.map(participants, MatchGroupInputUtil.prefixPartcipants(opponentIndex))
end

---@param mapInput table
---@param opponent table
---@param opponentIndex integer
---@return table<string, {player: string, played: boolean, cards: table}>
function MapFunctions.getPartyParticipants(mapInput, opponent, opponentIndex)
local players = opponent.match2players

local prefix = 't' .. opponentIndex .. 'p'

local participants = {}

Array.forEach(players, function(player, playerIndex)
participants[opponentIndex .. '_' .. playerIndex] = {
played = true,
player = player.name,
cards = CustomMatchGroupInput._readCards(mapInput[prefix .. playerIndex .. 'c']),
}
end)

return participants
end

---@param mapInput table
---@param participants table<string, {player: string, played: boolean, cards: table}>
---@return table
function MapFunctions.getExtraData(mapInput, participants)
local extradata = {
comment = mapInput.comment,
header = mapInput.header,
}

return Table.merge(extradata, MapFunctions.getCardsExtradata(participants))
end

--- additionally store cards info in extradata so we can condition on them
---@param participants table<string, {player: string, played: boolean, cards: table}>
---@return table
function MapFunctions.getCardsExtradata(participants)
local extradata = {}
local playerCount = {}
for participantKey, participant in Table.iter.spairs(participants) do
local opponentIndex = string.match(participantKey, '^(%d+)_')

playerCount[opponentIndex] = (playerCount[opponentIndex] or 0) + 1

local prefix = 't' .. opponentIndex .. 'p' .. playerCount[opponentIndex]
extradata[prefix .. 'tower'] = participant.cards.tower
-- participant.cards is an array plus the tower value ....
for cardIndex, card in ipairs(participant.cards) do
extradata[prefix .. 'c' .. cardIndex] = card
end
end

return extradata
end

---@param input string
---@return table
function CustomMatchGroupInput._readCards(input)
local cleanCard = FnUtil.curry(MatchGroupInputUtil.getCharacterName, CardNames)

FenrirWulf marked this conversation as resolved.
Show resolved Hide resolved
return Table.mapValues(Json.parseIfString(input) or {}, cleanCard)
end

return CustomMatchGroupInput
hjpalpha marked this conversation as resolved.
Show resolved Hide resolved
Loading