You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
13 KiB
305 lines
13 KiB
local _,rematch = ...
|
|
local L = rematch.localization
|
|
local C = rematch.constants
|
|
local settings = rematch.settings
|
|
rematch.savedTeams = {}
|
|
|
|
--[[
|
|
A team has this definition:
|
|
|
|
rematch.savedTeams[teamID] = {
|
|
teamID = "team:0", -- unique identifier of the team (required and persistent)
|
|
name = "", -- unique name of the team (required, case insensitive)
|
|
pets = {petID,petID,petID}, -- list of petIDs (or speciesIDs/"leveling"/"random:0"/"ignored") (required)
|
|
tags = {petTag,petTag,petTag}, -- list of petTags of petIDs (required)
|
|
[favorite = true,] -- whether team is favorited
|
|
[groupID = "group:0",] -- group for team (defaults to group:none)
|
|
[homeID = "group:0",] -- groupID the team belongs to when it's not favorited (or potential future group)
|
|
[notes = "",] -- notes for team
|
|
[targets = {targetID,targetID,targetID,...},] -- ordered list of targets
|
|
[preferences = {minHP=0,maxHP=0,minXP=0,maxXP=0,allowMM=true,expectedDD=0},]
|
|
[winrecord = {wins=0,losses=0,draws=0,battles=0},]
|
|
}
|
|
|
|
teamID is generated for each new team and always increments. Deleted teamIDs may be reused.
|
|
|
|
]]
|
|
|
|
Rematch5SavedTeams = {} -- actual savedvar for the teams (don't reference this directly)
|
|
|
|
-- some teams are not a savedvar but can be used/referenced like one and serve a special role
|
|
local metaTeams = {
|
|
empty = {teamID="empty",name="",pets={},tags={}}, -- a team with empty values (for resetting sideline or temporary)
|
|
sideline = {teamID="sideline",name="",pets={},tags={}}, -- teams being created/manipulated without affecting a live team
|
|
loadonly = {teamID="loadonly",name=BATTLE_PET_SLOTS,pets={},tags={}}, -- teams loaded without being saved
|
|
temporary = {teamID="temporary",name="",pets={},tags={}}, -- temporary holding place for teams
|
|
original = {teamID="original",name="",pets={},tags={}}, -- for save dialog to compare original team against modifications
|
|
counter = {teamID="counter",name=C.COUNTER_TEAM_NAME,pets={},tags={}}, -- for random counter teams from target panel
|
|
}
|
|
|
|
-- indexed by petID, the count of teams that contain the petID (updated in updatePets())
|
|
local numPetsByTeamID = {}
|
|
-- indexed by team name, the teamID that has the name
|
|
local teamIDsByName = {}
|
|
-- number of teams (updated during afterTeamsChange)
|
|
local numTeams = 0
|
|
|
|
-- ordered list of team events and the args to pass when they fire at the end of afterTeamsChanged
|
|
dispatch = {}
|
|
|
|
--[[ local functions ]]
|
|
|
|
-- returns true if the given team is structured properly
|
|
local function validateTeamStructure(team)
|
|
if not team or type(team)~="table" then -- this isn't even an attempt at a team
|
|
return false
|
|
elseif not team.teamID then -- all teams must have a teamID
|
|
return false
|
|
elseif not team.name then -- all teams must have a name
|
|
return false
|
|
elseif type(team.pets)~="table" then -- all teams must have a list of petIDs (even if empty)
|
|
return false
|
|
elseif type(team.tags)~="table" then -- all teams must have a list of petTags (even if empty)
|
|
return false
|
|
end
|
|
return true -- gauntlet passed, this team is valid
|
|
end
|
|
|
|
-- copies a team from the source to the destination; all except teamID
|
|
local function copyTeam(source,dest)
|
|
assert(type(dest)=="table" and dest.teamID,"Attempt to copy to a malformed team: "..(source.teamID or "nil").." to "..(type(dest)=="table" and "nil" or "non-tabnle"))
|
|
assert(validateTeamStructure(source),"Attempt to copy from a malformed team: "..(source.teamID or "nil").." to "..(dest.teamID or "nil"))
|
|
source.name = source.name:trim()
|
|
-- first wipe the destination of all but teamID
|
|
for k,v in pairs(dest) do
|
|
if k=="teamID" then
|
|
-- do nothing
|
|
elseif k=="pets" or k=="tags" then
|
|
wipe(v) -- pets and tags tables are mandatory
|
|
else
|
|
dest[k] = nil -- but all other tables (and non-tables beside teamID) are optional
|
|
end
|
|
end
|
|
-- now copy contents of source to dest
|
|
for k,v in pairs(source) do
|
|
if k=="teamID" then
|
|
-- never replace teamID
|
|
elseif type(v)=="table" then --k=="pets" or k=="tags" or k=="targets" or k=="preferences" then
|
|
dest[k] = CopyTable(v)
|
|
else
|
|
dest[k] = v
|
|
end
|
|
end
|
|
-- if non-meta team doesn't have a group after copy, put it in group:none
|
|
if not dest.groupID and not metaTeams[dest.teamID] then
|
|
dest.groupID = "group:none"
|
|
end
|
|
end
|
|
|
|
-- returns an unused team:<number> for use as a unique teamID
|
|
local function getNewUniqueTeamID()
|
|
local teamID = 1
|
|
while Rematch5SavedTeams["team:"..teamID] do
|
|
teamID = teamID + 1
|
|
end
|
|
return "team:"..teamID
|
|
end
|
|
|
|
-- rematch.savedTeams[key] will get the savedvar unless it's a meta teamID
|
|
local function getter(self,key)
|
|
if metaTeams[key] then
|
|
return metaTeams[key]
|
|
else -- every other teamID should be a number
|
|
return Rematch5SavedTeams[key] -- otherwise returns the savedvar team of the given temID
|
|
end
|
|
end
|
|
|
|
-- rematch.savedTeams[key] = {team} will "copy" the contents of {team} to rematch.savedTeams[key]
|
|
-- if {team} is nil, it will empty metateams or nil savedvar teams
|
|
local function setter(self,key,value)
|
|
if key=="empty" then
|
|
return -- empty team should never be modified
|
|
elseif value==nil then -- if nil'ing a team
|
|
if metaTeams[key] then -- if nil'ing a metateam, empty it but don't nil it
|
|
copyTeam(metaTeams.empty,metaTeams[key])
|
|
else -- otherwise nil the saved team
|
|
Rematch5SavedTeams[key] = nil
|
|
tinsert(dispatch,{"REMATCH_TEAM_DELETED",key})
|
|
rematch.savedTeams:TeamsChanged()
|
|
end
|
|
elseif validateTeamStructure(value) then -- if assigning another team to this team
|
|
value.name = value.name:trim()
|
|
if metaTeams[key] then -- if setting a metaTeam value, then copy the team
|
|
copyTeam(value,metaTeams[key])
|
|
elseif Rematch5SavedTeams[key] then -- team already exists, replace contents
|
|
copyTeam(value,Rematch5SavedTeams[key])
|
|
tinsert(dispatch,{"REMATCH_TEAM_UPDATED",key})
|
|
rematch.savedTeams:TeamsChanged()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- updates numPetsByTeamID with all petIDs in a team and how many teams they belong to
|
|
-- while here looping through teams, update numTeams to the number of teams
|
|
local function updatePets()
|
|
local pets = numPetsByTeamID
|
|
wipe(pets)
|
|
numTeams = 0
|
|
for teamID,team in rematch.savedTeams:AllTeams() do
|
|
numTeams = numTeams + 1
|
|
for i=1,3 do
|
|
local petID = team.pets[i]
|
|
if petID then
|
|
if type(petID)=="number" and ((i==2 and petID==team.pets[1]) or (i==3 and (petID==team.pets[1] or petID==team.pets[2]))) then
|
|
-- for speciesIDs, we only want to count it once per team; so do nothing if we already added this speciesID
|
|
elseif not pets[petID] then
|
|
pets[petID] = 1 -- first time encountering petID, start count at 1
|
|
else
|
|
pets[petID] = pets[petID] + 1 -- already-encountede petID, increment count
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- updates teamIDsByName with all team names and the teamID the name is used by
|
|
local function updateNames()
|
|
wipe(teamIDsByName)
|
|
for teamID,team in rematch.savedTeams:AllTeams() do
|
|
teamIDsByName[team.name:lower()] = teamID
|
|
end
|
|
end
|
|
|
|
-- runs 0 frames after rematch.savedTeams:TeamsChanged() to do housekeeping and fires REMATCH_TEAMS_CHANGED event
|
|
local function afterTeamsChanged()
|
|
rematch.savedTargets:Update() -- update targets based on teams
|
|
rematch.savedGroups:Update() -- update groups based on teams
|
|
updatePets()
|
|
updateNames()
|
|
-- if any REMATCH_TEAM_CREATED, REMATCH_TEAM_DELETED, REMATCH_TEAM_UPDATED events are waiting to fire them, fire them here
|
|
if #dispatch>0 then
|
|
for _,info in ipairs(dispatch) do
|
|
rematch.events:Fire(info[1],info[2],info[3],info[4])
|
|
end
|
|
wipe(dispatch)
|
|
end
|
|
-- and then fire a generic REMATCH_TEAMS_CHANGED
|
|
rematch.events:Fire("REMATCH_TEAMS_CHANGED")
|
|
if rematch.frame:IsVisible() then
|
|
rematch.frame:Update()
|
|
end
|
|
end
|
|
|
|
--[[ public functions ]]
|
|
|
|
-- iterator for all teams (a normal loop over rematch.savedTeams is a loop over a near-empty table with just a couple functions)
|
|
function rematch.savedTeams:AllTeams()
|
|
return next, Rematch5SavedTeams, nil
|
|
end
|
|
|
|
-- empties a metateam ("sideline" or "temporary")
|
|
-- to reset a regular team, reset a sideline and assign it to the team
|
|
function rematch.savedTeams:Reset(teamID)
|
|
assert(metaTeams[teamID],"Reset must be a named metaTeam such as \"sideline\" or \"temporary\"")
|
|
-- to reset a regular team, reset a sideline and assign it to the team
|
|
copyTeam(metaTeams.empty,metaTeams[teamID])
|
|
end
|
|
|
|
-- constructor for a new team; creates a new teamID in Rematch5SavedTeams from the sideline team
|
|
function rematch.savedTeams:Create()
|
|
local sideline = metaTeams.sideline
|
|
assert(sideline,"Sideline team is malformed. Can't create a new team.")
|
|
assert(sideline.name:len()>0,"Sideline team has no name. Can't create a new team.")
|
|
-- create a new table from a copy of the sideline
|
|
local team = CopyTable(sideline)
|
|
-- if name is already taken, make a unique name by appending (2) after it (or 3, 4, etc.)
|
|
team.name = rematch.savedTeams:GetUniqueName(team.name:trim())
|
|
-- assign a unique teamID
|
|
team.teamID = getNewUniqueTeamID()
|
|
-- and a groupID if none was in sideline
|
|
if not team.groupID then
|
|
team.groupID = "group:none"
|
|
end
|
|
-- and save
|
|
Rematch5SavedTeams[team.teamID] = team
|
|
tinsert(dispatch,{"REMATCH_TEAM_CREATED",team.teamID})
|
|
rematch.savedTeams:TeamsChanged()
|
|
return team
|
|
end
|
|
|
|
-- deletes a non-meta team
|
|
function rematch.savedTeams:DeleteTeam(teamID)
|
|
if teamID and rematch.savedTeams[teamID] then
|
|
rematch.savedTeams[teamID] = nil
|
|
rematch.savedTeams:TeamsChanged()
|
|
end
|
|
end
|
|
|
|
-- returns a unique team name, either based off the given name or "New Team", appending a (2) (or (3), (4), etc.)
|
|
-- if the name is already taken
|
|
function rematch.savedTeams:GetUniqueName(name)
|
|
updateNames()
|
|
name = (name or L["New Team"]):trim()
|
|
if not teamIDsByName[tostring(name):lower()] then
|
|
return name -- name given (or "New Team") is not taken yet, return it
|
|
end
|
|
name = name:trim():gsub(" %(%d+%)$","") -- take off any trailing (2)s
|
|
local num = 1 -- this will be incremented to 2
|
|
local newName
|
|
repeat
|
|
num = num + 1
|
|
newName = format("%s (%d)",name,num)
|
|
until newName and not teamIDsByName[newName:lower()]
|
|
return newName
|
|
end
|
|
|
|
-- returns the number of teams with the given petID (can be a speciesID for uncollected pets)
|
|
function rematch.savedTeams:GetNumTeamsWithPet(petID)
|
|
return petID and numPetsByTeamID[petID] or 0
|
|
end
|
|
|
|
-- returns the teamID of the team with the given name
|
|
function rematch.savedTeams:GetTeamIDByName(name)
|
|
return name and teamIDsByName[name:lower()]
|
|
end
|
|
|
|
-- returns true if the teamID is not a "meta" team like "counter" or "sideline"
|
|
function rematch.savedTeams:IsUserTeam(teamID)
|
|
return (teamID and not metaTeams[teamID] and rematch.savedTeams[teamID]) and true or false
|
|
end
|
|
|
|
-- returns the number of teams saved (excludes meta teams)
|
|
function rematch.savedTeams:GetNumTeams()
|
|
return numTeams
|
|
end
|
|
|
|
-- deletes all teams
|
|
function rematch.savedTeams:Wipe()
|
|
Rematch5SavedTeams = {}
|
|
tinsert(dispatch,{"REMATCH_TEAMS_WIPED"})
|
|
rematch.savedTeams:TeamsChanged()
|
|
end
|
|
|
|
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
-- !! !!
|
|
-- !! CALL THIS WHEN TARGETS/PETS/NAMES ON A TEAM CHANGES !!
|
|
-- !! !!
|
|
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
-- this does a 0-frame timer before running housekeeping from changing teams; the validation is relatively
|
|
-- expensive, so a 0-frame timer is used out of an abundance of caution that it doesn't happen multiple times in a row
|
|
function rematch.savedTeams:TeamsChanged(now)
|
|
if now then
|
|
afterTeamsChanged() -- if now is true, do an immediate update (only do this when necessary; eg dragging teams)
|
|
else
|
|
rematch.timer:Start(0,afterTeamsChanged)
|
|
end
|
|
end
|
|
|
|
-- add getter and setter metamethods that get/set to savedvar instead of rematch.savedTeams
|
|
setmetatable(rematch.savedTeams,{__index=getter,__newindex=setter})
|
|
|
|
-- after pets are loaded (this fires from roster), do housekeeping (roster will already call ValidateAllTeams)
|
|
rematch.events:Register(rematch.savedTeams,"REMATCH_PETS_LOADED",function()
|
|
tinsert(dispatch,{"REMATCH_TEAMS_READY"})
|
|
rematch.savedTeams:TeamsChanged()
|
|
end)
|
|
|