gGroupCalendar_MessagePrefix0 = "GC2"; gGroupCalendar_MessagePrefix = gGroupCalendar_MessagePrefix0.."/"; gGroupCalendar_MessagePrefixLength = string.len(gGroupCalendar_MessagePrefix); gGroupCalendar_Channel = { Name = nil, NameLower = nil, ID = nil, Password = nil, AutoPlayer = nil, Status = "Disconnected", StatusMessage = nil, GotTooManyChannelsMessage = false, }; gGroupCalendar_Queue = { TasksDelay = 0, TasksElapsed = 0, Tasks = {}, RequestsDelay = 0, RequestsElapsed = 0, Requests = {[1] = {}, [2] = {}}, InboundDelay = 0, InboundMessages = {}, InboundLastSender = nil, InboundTimeSinceLastMessage = 0, OutboundDelay = 0, OutboundMessages = {}, OutboundTimeSinceLastMessage = 0, DatabaseUpdates = {}, RSVPUpdates = {}, }; gCalendarNetwork_UserTrustCache = { }; gCalendarNetwork_RequestDelay = { Init = 120, ShortInit = 5, SelfUpdateRequest = 1, ExternalUpdateRequest = 30, OwnedNotices = 300, -- Allow time for updates to arrive before we'll start advertising databases JoinChannel2 = 2, OwnedUpdate = 3, ProxyUpdateMin = 6, ProxyUpdateRange = 4, RFUMin = 1, RFURange = 5, OwnedNOURange = 2, ProxyNOUMin = 6, ProxyNOURange = 4, InboundQueue = 0.2, OutboundQueue = 0.2, RequestQueue = 0.2, -- Outbound queue delay 1 - 5 seconds OutboundQueueGapMin = 1, -- Delay for after last inbound message was processed OutboundQueueGapWidth = 4, -- Random delay after the min time -- Request queue delay 1 - 5 seconds RequestQueueGapMin = 1, -- Delay for after last inbound or outbound message was processed RequestQueueGapWidth = 4, -- Random delay after the min time GuildUpdateAutoConfig = 2, CheckDatabaseTrust = 10, GuildRosterUpdate = 1, -- Maximum age for an update is two minutes MaximumUpdateAge = 120, }; gGroupCalendar_EnableUpdates = false; -- Don't allow the user to send updates to any databases -- until we're sure that his databases are up-to-date themselves gGroupCalendar_EnableSelfUpdates = false; -- Don't allow the user to send updates for their own databases -- until well after they're sure nobody has updates for them function CalendarNetwork_GetChannelStatus() return gGroupCalendar_Channel.Status; end function CalendarNetwork_SetChannel(pChannel, pPassword) -- Leave the channel and return if the new channel is nil if not pChannel then CalendarNetwork_LeaveChannel(); return true; end -- Just return if nothing is actually changing if gGroupCalendar_Channel.Name and strupper(gGroupCalendar_Channel.Name) == strupper(pChannel) and gGroupCalendar_Channel.Password == pPassword and gGroupCalendar_Channel.Status == "Connected" then return false; -- return false to indicate that no change was made end -- Leave the old channel and join the new one CalendarNetwork_LeaveChannel(); return CalendarNetwork_JoinChannel(pChannel, pPassword); end function CalendarNetwork_JoinChannel(pChannelName, pPassword) if not pChannelName or pChannelName == "" or gGroupCalendar_Channel.Disconnected then return false; end -- There seems to be some sort of bug with passworded channels getting -- screwed up if the UI is reloaded and the solution appears to be to -- leave and re-join the channel. Therefore, the code below... LeaveChannelByName(pChannelName); CalendarNetwork_LeftChannel(); CalendarNetwork_SetChannelStatus("Initializing"); CalendarNetwork_QueueTask( CalendarNetwork_JoinChannel2, {mChannelName = pChannelName, mPassword = pPassword}, gCalendarNetwork_RequestDelay.JoinChannel2, "JOINCHANNEL2"); return true; end function CalendarNetwork_JoinChannelFailed() if gGroupCalendar_Channel.GotTooManyChannelsMessage then CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cTooManyChannels); else CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cJoinChannelFailed); end end function CalendarNetwork_LeftChannel() gGroupCalendar_Channel.Name = nil; gGroupCalendar_Channel.Password = nil; gGroupCalendar_Channel.NameLower = nil; gGroupCalendar_Channel.ID = nil; CalendarNetwork_SetChannelStatus("Disconnected"); end function CalendarNetwork_SetChannelStatus(pStatus, pStatusMessage) gGroupCalendar_Channel.Status = pStatus; gGroupCalendar_Channel.StatusMessage = pStatusMessage; GroupCalendar_ChannelChanged(); end function CalendarNetwork_SystemMessage(pMessage) if pMessage == ERR_TOO_MANY_CHAT_CHANNELS then gGroupCalendar_Channel.GotTooManyChannelsMessage = true; if gGroupCalendar_Channel.Status == "Error" then CalendarNetwork_SetChannelStatus("Error", pMessage); end elseif pMessage == INSTANCE_SAVED then -- Fetch the raid info so the reset events can be logged on -- the calendar RequestRaidInfo(); elseif pMessage == NO_RAID_INSTANCES_SAVED then EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase); elseif pMessage == RAID_INSTANCE_INFO_HDR then -- Remove existing events since they will be replaced by new incoming info EventDatabase_RemoveSavedInstanceEvents(gGroupCalendar_UserDatabase); else local vStartIndex, vEndIndex, vName, vID, vDays, vHours, vMinutes, vSeconds = string.find(pMessage, "(.+) %(ID=(%x+)%): (%d+)d (%d+)h (%d+)m (%d+)s"); if vStartIndex then -- Calculate the number of seconds until the instance resets local vRemainingDateTime60 = tonumber(vDays) * gCalendarSecondsPerDay + Calendar_ConvertHMSToTime60(tonumber(vHours), tonumber(vMinutes), tonumber(vSeconds)); local vServerResetDate, vServerResetTime = Calendar_GetServerDateTimeFromSecondsOffset(vRemainingDateTime60); EventDatabase_ScheduleSavedInstanceEvent(gGroupCalendar_UserDatabase, vName, vServerResetDate, vServerResetTime); end end end function CalendarNetwork_JoinChannel2(pParams) if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_JoinChannel: "..pParams.mChannelName); end gGroupCalendar_Channel.GotTooManyChannelsMessage = false; gGroupCalendar_Channel.Name = pParams.mChannelName; local vChannelName = GetChannelName(pParams.mChannelName); local vZoneChannel; local vChannelAlreadyExists = false; if vChannelName and vChannelName ~= 0 then if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("Found existing channel "..pParams.mChannelName.." ("..vChannelName..")"); end vChannelName = pParams.mChannelName; vChannelAlreadyExists = true; else if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("Channel "..pParams.mChannelName.." not found ("..vChannelName..") joining channel..."); end vZoneChannel, vChannelName = JoinChannelByName(pParams.mChannelName, pParams.mPassword, DEFAULT_CHAT_FRAME:GetID()); if not vZoneChannel then CalendarNetwork_JoinChannelFailed(); return false; end if not vChannelName then vChannelName = pParams.mChannelName; end end -- Remove the channel from the chat frame so the user doesn't -- have to watch all that data comm. (if we're running debug -- code then leave the setting alone) ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, pParams.mChannelName); if gCalendar_DebugFrame then ChatFrame_AddChannel(gCalendar_DebugFrame, pParams.mChannelName); end gGroupCalendar_Channel.Password = pParams.mPassword; gGroupCalendar_Channel.NameLower = strlower(pParams.mChannelName); gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name); if not gGroupCalendar_Channel.ID or gGroupCalendar_Channel.ID == 0 then gGroupCalendar_Channel.ID = nil; CalendarNetwork_JoinChannelFailed(); return false; end if vChannelAlreadyExists then CalendarNetwork_JoinedChannel(); end return true; end function CalendarNetwork_LeaveChannel() if gGroupCalendar_Channel.Name and gGroupCalendar_Channel.ID and gGroupCalendar_Channel.Status ~= "Suspended" then LeaveChannelByName(gGroupCalendar_Channel.Name); CalendarNetwork_LeftChannel(); end end function CalendarNetwork_JoinedChannel() CalendarNetwork_SetChannelStatus("Connected"); if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_JoinChannel succeeded channel "..gGroupCalendar_Channel.Name.." ID "..gGroupCalendar_Channel.ID); end -- Send update requests/notices CalendarNetwork_QueueTask(CalendarNetwork_SendNotices, nil, gCalendarNetwork_RequestDelay.SelfUpdateRequest, "SELFUPDATE"); end function CalendarNetwork_ChannelNotice(pChannelMessage, pChannelName, pChannelID, pActualChannelName) -- Decode the channel name if it's in the nn. format local vChannelName = pChannelName; for vFoundNumber, vFoundName in string.gfind(pChannelName, "(%d)%. (.+)") do vChannelName = vFoundName; end -- Just leave if it's not a channel we're interested in local vIsDataChannel = strlower(vChannelName) == gGroupCalendar_Channel.NameLower; -- if pChannelMessage == "YOU_JOINED" then -- If it's one of the system channels, then shorten the initialization -- timer if one is present if pChannelID == 1 then CalendarNetwork_SystemChannelJoined(); end if not vIsDataChannel then return; end if pChannelID > 0 then CalendarNetwork_JoinedChannel(); else -- Joining failed CalendarNetwork_JoinChannelFailed(); end elseif pChannelMessage == "YOU_LEFT" then if not vIsDataChannel then return; end if not gGroupCalendar_Channel.ID then return; end CalendarNetwork_LeftChannel(); elseif pChannelMessage == "WRONG_PASSWORD" then if not vIsDataChannel then return; end CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cWrongPassword); end end function CalendarNetwork_SuspendChannel() if gGroupCalendar_Channel.Status == "Suspended" or not gGroupCalendar_Channel.Name or not gGroupCalendar_Channel.ID then return; end LeaveChannelByName(gGroupCalendar_Channel.Name); gGroupCalendar_Channel.ID = nil; CalendarNetwork_SetChannelStatus("Suspended"); end function CalendarNetwork_ResumeChannel() if not gGroupCalendar_Channel.Status ~= "Suspended" then return; end JoinChannelByName(gGroupCalendar_Channel.Name, gGroupCalendar_Channel.Password, DEFAULT_CHAT_FRAME:GetID()); gGroupCalendar_Channel.ID = GetChannelName(gGroupCalendar_Channel.Name); ChatFrame_RemoveChannel(DEFAULT_CHAT_FRAME, gGroupCalendar_Channel.Name); CalendarNetwork_SetChannelStatus("Connected"); end function CalendarNetwork_UnpackIndexedList(...) local vList = {}; for vIndex = 1, arg.n, 2 do vList[tonumber(arg[vIndex])] = arg[vIndex + 1]; end return vList; end function CalendarNetwork_GetChannelList() local vChannelList = CalendarNetwork_UnpackIndexedList(GetChannelList()); return vChannelList; end function CalendarNetwork_CalendarLoaded() -- Set up a deferred initialization CalendarNetwork_SetChannelStatus("Starting"); -- See if there are system channels yet and use -- the shorter delay if there are local vDelay = gCalendarNetwork_RequestDelay.Init; local vChannelList = CalendarNetwork_GetChannelList(); if vChannelList[1] then vDelay = gCalendarNetwork_RequestDelay.ShortInit; end -- CalendarNetwork_QueueTask(CalendarNetwork_Initialize, nil, vDelay, "INIT"); end function CalendarNetwork_SystemChannelJoined() CalendarNetwork_SetTaskDelay("INIT", gCalendarNetwork_RequestDelay.ShortInit); end function CalendarNetwork_ProcessCommandString(pSender, pTrustLevel, pCommandString, pCurrentTimeStamp) local vCommand = CalendarNetwork_ParseCommandString(pCommandString); if not vCommand then if gGroupCalendar_Settings.Debug then Calendar_DebugMessage("ProcessCommandString: Couldn't parse ["..pSender.."]:"..pCommandString); end return false; end return CalendarNetwork_ProcessCommand(pSender, pTrustLevel, vCommand, pCurrentTimeStamp); end function CalendarNetwork_ProcessCommand(pSender, pTrustLevel, pCommand, pCurrentTimeStamp) -- Age out old updates which haven't been seen in a while local vMinimumUpdateTime = pCurrentTimeStamp - gCalendarNetwork_RequestDelay.MaximumUpdateAge; CalendarNetwork_KillOldUpdates("DB", gGroupCalendar_Queue.DatabaseUpdates, vMinimumUpdateTime); CalendarNetwork_KillOldUpdates("RAT", gGroupCalendar_Queue.RSVPUpdates, vMinimumUpdateTime); -- local vOpcode = pCommand[1].opcode; local vOperands = pCommand[1].operands; table.remove(pCommand, 1); if vOpcode == "DB" then local vUserName = vOperands[1]; local vDatabaseID = tonumber(vOperands[2]); local vRevision = tonumber(vOperands[3]); local vAuthRevision = tonumber(vOperands[4]); -- If the sender is using wildcards, fetch the path from -- the update records if vUserName == "*" then local vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender); if not vDatabaseUpdate then return; end vUserName = vDatabaseUpdate.mUserName; vDatabaseID = vDatabaseUpdate.mDatabaseID; vRevision = vDatabaseUpdate.mRevision; vAuthRevision = nil; end -- if not vRevision then vRevision = 0; end if CalendarTrust_UserIsTrusted(vUserName) then -- only accept databases for trusted users (or our own) CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand); else if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for DB command"); end end elseif vOpcode == "RAT" then local vUserName = vOperands[1]; local vDatabaseID = tonumber(vOperands[2]); local vRevision = tonumber(vOperands[3]); local vAuthRevision = tonumber(vOperands[4]); -- If the sender is using wildcards, fetch the path from -- the update records if vUserName == "*" then local vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender); if not vRSVPUpdate then return; end vUserName = vRSVPUpdate.mUserName; vDatabaseID = vRSVPUpdate.mDatabaseID; vRevision = vRSVPUpdate.mRevision; vAuthRevision = nil; end -- if not vRevision then vRevision = 0; end if CalendarTrust_UserIsTrustedForRSVPs(vUserName) then -- only accept RSVP for trusted users (or our own) CalendarNetwork_ProcessRSVPCommand(pSender, vUserName, vDatabaseID, vRevision, vAuthRevision, pCommand); else if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("ChannelMessageReceived: User "..vUserName.." is not trusted for RAT command"); end end elseif vOpcode == "GLD" then local vGuildName = vOperands[1]; local vMinRank = tonumber(vOperands[2]); CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, vGuildName, vMinRank, pCommand); elseif vOpcode == "ALL" then CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand); elseif vOpcode == "VER" then local vDatabase = EventDatabase_GetDatabase(pSender, true); if vDatabase then vDatabase.AddonVersion = vOperands[1]; end else if gGroupCalendar_Settings.Debug then Calendar_DebugMessage("ProcessCommand: Unknown opcode "..vOpcode); end end end function CalendarNetwork_ProcessChangesRFU(pChanges, pDatabaseTag, pPlayerOwned, pPriority, pUserName, pDatabaseID, pRevision, pAuthRevision) if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage(pDatabaseTag.." changes update requested for: "..pUserName..","..pRevision); end -- Cancel a queued RFU for the same database if it's redundant CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision) -- Just bail out if we don't have the requested database if not pChanges then return; end -- Create the request local vRequest; local vFromRevision; local vForceUpdate = false; if pChanges.ID ~= pDatabaseID then if pPlayerOwned or pChanges.ID > pDatabaseID then vFromRevision = 0; vForceUpdate = true; else if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("Not sending "..pDatabaseTag.." update: Requested database isn't available for "..pUserName..","..pDatabaseID); end return; end else if pPlayerOwned and pAuthRevision and pAuthRevision < pRevision then vFromRevision = pAuthRevision; else vFromRevision = pRevision; end end vRequest = CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName); if vRequest then if vFromRevision < vRequest.mRevision then if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("Changing existing "..pDatabaseTag.." request to revision "..vFromRevision.." for "..pUserName); end vRequest.mRevision = vFromRevision; else if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("Existing request for "..pDatabaseTag.." "..pUserName..","..pRevision); end end CalendarNetwork_SetRequestPriority(vRequest, pPriority); elseif pChanges.Revision > vFromRevision or vForceUpdate then vRequest = {}; vRequest.mOpcode = pDatabaseTag.."_UPD"; vRequest.mUserName = pUserName; vRequest.mDatabaseID = pChanges.ID; vRequest.mRevision = vFromRevision; -- Determine a delay local vDelay; if pPlayerOwned then vDelay = gCalendarNetwork_RequestDelay.OwnedUpdate; else vDelay = gCalendarNetwork_RequestDelay.ProxyUpdateMin + math.random() * gCalendarNetwork_RequestDelay.ProxyUpdateRange; end CalendarNetwork_QueueRequest(vRequest, vDelay, pPriority); end end function CalendarNetwork_GetDatabaseChanges(pUserName, pCreate) local vDatabase = EventDatabase_GetDatabase(pUserName, pCreate); if vDatabase then return vDatabase, vDatabase.Changes, vDatabase.IsPlayerOwned; else return nil, nil, nil; end end function CalendarNetwork_GetDatabaseRSVPChanges(pUserName, pCreate) local vDatabase = EventDatabase_GetDatabase(pUserName, pCreate); if vDatabase then return vDatabase, vDatabase.RSVPs, vDatabase.IsPlayerOwned; else return nil, nil, nil; end end function CalendarNetwork_ProcessDatabaseCommand(pSender, pTrustLevel, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand) local vOpcode = pCommand[1].opcode; local vOperands = pCommand[1].operands; table.remove(pCommand, 1); local vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseChanges(pUserName, false); local vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName; if vOpcode ~= "RFU" and vOpcode ~= "RFV" and pTrustLevel < 2 then -- Players with moderate trust levels (ie, fellow guild members) are -- only allowed to requeust updates, not provide updates if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarNetwork_ProcessDatabaseCommand: Ignoring "..vOpcode.." request from "..pSender); end return; end if vOpcode == "RFU" then -- If the sender is seen transmitting an RFU while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersDatabaseUpdates(pSender); -- if vChanges then -- Give the owner high-priority to help ensure that he can roam successfully local vPriority; if pSender == pUserName then vPriority = 1; else vPriority = 2; end CalendarNetwork_ProcessChangesRFU(vChanges, "DB", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision); end elseif vOpcode == "RFV" then -- If the sender is seen transmitting an RFV while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersDatabaseUpdates(pSender); -- if vIsThisPlayerOwned then CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString); end elseif vOpcode == "NOU" then -- If the sender is seen transmitting an NOU while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersDatabaseUpdates(pSender); -- local vCurrentRevision = 0; local vDatabaseIDChanged = false; if not vIsThisPlayerOwned then CalendarNetwork_CancelRedundantNOURequests(vChanges, "DB", pSender, pUserName, pDatabaseID, pRevision); end if not vDatabase then vDatabase = EventDatabase_AssumeDatabase(pUserName); end if vDatabase then -- Ignore external updates to our own databases if vDatabase.IsPlayerOwned then -- If someone sent out a NOU for one of our databases and it's older than -- ours and ours is now empty, send out a DEL to let them know that it's newer -- and empty now if CalendarChanges_IsEmpty(vChanges) then CalendarNetwork_SendEmptyChanges(vChanges, "DB", pUserName); end return; end -- Ignore the update if the ID is older than the one we have -- unless it's from the owner if vChanges and pDatabaseID < vChanges.ID and pSender ~= vDatabase.UserName then return; end -- Force a RFU if the ID is changing if not vChanges or vChanges.ID ~= pDatabaseID then vDatabaseIDChanged = true; else vCurrentRevision = vChanges.Revision; if not vCurrentRevision then Calendar_ErrorMessage("Changes for "..pUserName.." has nil revision"); return; end end end if vDatabaseIDChanged or vCurrentRevision < pRevision then CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, pRevision); end elseif vOpcode == "UPD" then -- If the sender is seen transmitting an UPD while he has another database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName); -- Begin a database update local vSinceRevision = tonumber(vOperands[1]); if not vSinceRevision then if gGroupCalendar_Settings.DebugErrors then Calendar_DebugMessage("GroupCalendar: DB UPD received from "..pSender.." for "..pUserName.." with no SinceRevision"); end return; end if not vIsThisPlayerOwned then -- If we're waiting to request this same update, cancel the request CalendarNetwork_CancelRedundantRFURequest("DB", pUserName, pDatabaseID, vSinceRevision); -- If we're waiting to send this same update, cancel the request CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, vSinceRevision); end CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision); elseif vOpcode == "DEL" then -- Delete the database since it's empty if not vIsThisPlayerOwned then CalendarNetwork_CancelRedundantUPDRequests(vChanges, "DB", pUserName, pDatabaseID, pRevision, 0); end CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision); elseif vOpcode == "EVT" then -- Event data local vEventID = tonumber(vOperands[1]); CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, vEventID, pCommand); elseif vOpcode == "END" then -- End a database update local vSinceRevision = tonumber(vOperands[1]); CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision); end end function CalendarNetwork_ProcessRSVPCommand(pSender, pUserName, pDatabaseID, pRevision, pAuthRevision, pCommand) local vOpcode = pCommand[1].opcode; local vOperands = pCommand[1].operands; local vOperandString = pCommand[1].operandString; table.remove(pCommand, 1); local vDatabase, vChanges, vIsPlayerOwned = CalendarNetwork_GetDatabaseRSVPChanges(pUserName, false); local vIsThisPlayerOwned = vIsPlayerOwned and pUserName == gGroupCalendar_PlayerName; if vOpcode == "RFU" then -- If the sender is seen transmitting an RFU while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersRSVPUpdates(pSender); -- if vChanges then -- Give the owner high-priority to help ensure that he can roam successfully local vPriority; if pSender == pUserName then vPriority = 1; else vPriority = 2; end CalendarNetwork_ProcessChangesRFU(vChanges, "RAT", vIsThisPlayerOwned, vPriority, pUserName, pDatabaseID, pRevision, pAuthRevision); end elseif vOpcode == "NOU" then -- If the sender is seen transmitting a NOU while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersRSVPUpdates(pSender); -- local vCurrentRevision = 0; local vDatabaseIDChanged = false; if not vIsThisPlayerOwned then CalendarNetwork_CancelRedundantNOURequests(vChanges, "RAT", pSender, pUserName, pDatabaseID, pRevision); end if vDatabase then -- Ignore external updates to our own databases if vDatabase.IsPlayerOwned then -- If someone sent out a NOU for one of our databases and it's older than -- ours and ours is now empty, send out a DEL to let them know that it's newer -- and empty now if CalendarChanges_IsEmpty(vChanges) then CalendarNetwork_SendEmptyChanges(vChanges, "RAT", pUserName); end return; end -- Ignore the update if the ID is older than the one we have -- unless it's from the owner if vChanges and pDatabaseID < vChanges.ID and pSender ~= vDatabase.UserName then return; end -- Purge the existing database if the ID is changing if not vChanges or vChanges.ID ~= pDatabaseID then vDatabaseIDChanged = true; else vCurrentRevision = vChanges.Revision; end end if vDatabaseIDChanged or vCurrentRevision < pRevision then CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, pRevision); end elseif vOpcode == "UPD" then -- If the sender is seen transmitting an UPD while he has a database update -- pending then it probably means he d/c'd. Kill the database update in that -- case CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName); -- -- Begin a RSVP database update local vSinceRevision = tonumber(vOperands[1]); if not vSinceRevision then if gGroupCalendar_Settings.DebugErrors then Calendar_DebugMessage("GroupCalendar: RAT UPD received from "..pSender.." for "..pUserName.." with no SinceRevision"); end return; end if not vIsThisPlayerOwned then -- If we're waiting to ask for an update to this database, cancel the request CalendarNetwork_CancelRedundantRFURequest("RAT", pUserName, pDatabaseID, vSinceRevision); -- If we're waiting to send this same update, cancel the request CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, vSinceRevision); end CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision); elseif vOpcode == "DEL" then -- Delete the database since it's empty if not vIsThisPlayerOwned then CalendarNetwork_CancelRedundantUPDRequests(vChanges, "RAT", pUserName, pDatabaseID, pRevision, 0); end CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision); elseif vOpcode == "EVT" then -- Event data CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vOperandString); elseif vOpcode == "END" then -- End a RSVP update local vSinceRevision = tonumber(vOperands[1]); CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, vSinceRevision); end end function CalendarNetwork_ProcessGuildCommand(pSender, pTrustLevel, pGuildName, pMinRank, pCommand) -- Ignore guild commands if they're not directed at the player's guild -- or not at the player's rank if pGuildName ~= gGroupCalendar_PlayerGuild or gGroupCalendar_PlayerGuildRank > pMinRank then return; end -- Pass it on to the ALL handler for further processing CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand); end function CalendarNetwork_ProcessAllCommand(pSender, pTrustLevel, pCommand) local vOpcode = pCommand[1].opcode; local vOperands = pCommand[1].operands; table.remove(pCommand, 1); if vOpcode == "RFU" then CalendarNetwork_SendAllRevisionNotices(); elseif vOpcode == "RFV" then CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString); end end function CalendarNetwork_DatabaseIsNewer(pDatabaseID1, pRevision1, pDatabaseID2, pRevision2) if pDatabaseID2 > pDatabaseID1 then return true; elseif pDatabaseID2 < pDatabaseID1 then return false; else return pRevision2 > pRevision1; end end function CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, pDatabase, pChanges) -- Updates for our own databases are always worse if -- we're on the toon if pDatabase.IsPlayerOwned and pUserName == gGroupCalendar_PlayerName then return false; end -- Updates from the owner are always better than ours -- as are updates when our list is empty if string.lower(pSender) == string.lower(pUserName) or not pChanges then return true; end -- If the revision is higher than what we have it's better return CalendarNetwork_DatabaseIsNewer(pChanges.ID, pChanges.Revision, pDatabaseID, pRevision); end function CalendarNetwork_DeleteDatabase(pSender, pUserName, pDatabaseID, pRevision) -- Get the database local vDatabase = EventDatabase_GetDatabase(pUserName, false); -- Nothing to do if we don't even have the database if not vDatabase then return; end -- Bail out if our stuff is better if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.Changes) then return; end -- Empty the specified changelist EventDatabase_PurgeDatabase(vDatabase, pDatabaseID); vDatabase.Changes.Revision = pRevision; end function CalendarNetwork_BeginDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision) -- If the same sender has a pending update already in progress, kill it CalendarNetwork_KillSendersDatabaseUpdates(pSender, pUserName); -- Get the database local vDatabase = EventDatabase_GetDatabase(pUserName, false); -- Ignore updates for our own databases if vDatabase and vDatabase.IsPlayerOwned and not gGroupCalendar_Settings.AllowSelfUpdate then return; end local vChanges; if vDatabase then vChanges = vDatabase.Changes; else vChanges = nil; end -- Ignore the update if it isn't from the owner and -- our database ID is newer if string.lower(pSender) ~= string.lower(pUserName) and vChanges then if (vChanges.ID > pDatabaseID) or (vChanges.ID == pDatabaseID and vChanges.Revision >= pRevision) then return; end end -- If it's from the owner and isn't the same revision as ours, -- then delete the database. If the update is from revision -- zero go ahead with the update otherwise ignore it and request -- and update from zero if string.lower(pSender) == string.lower(pUserName) and vChanges and vChanges.ID ~= pDatabaseID then EventDatabase_PurgeDatabase(vDatabase, pDatabaseID); if pSinceRevision ~= 0 then -- Request an update and exit CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, 0) return; end end -- See if there's another update for the same database already in progress local vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(nil, pUserName, nil); if vDatabaseUpdate then -- If the new update is from the owner or -- the old one isn't from the owner and is a higher revision, -- then cancel the old update if string.lower(pSender) == string.lower(pUserName) or (vDatabaseUpdate.mSender ~= vDatabaseUpdate.mUserName and CalendarNetwork_DatabaseIsNewer(vDatabaseUpdate.mDatabaseID, vDatabaseUpdate.mRevision, pDatabaseID, pRevision)) then CalendarNetwork_CancelDatabaseUpdate(nil, pUserName, nil); -- Otherwise cancel this one else return; end end if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_BeginDatabaseUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender); end -- Can't accept updates which don't cover the last revision we received if vChanges and pSinceRevision > vChanges.Revision then -- Ask for another copy of the update starting with the revision we need CalendarNetwork_QueueRFURequest(pUserName, "DB", pDatabaseID, vChanges.Revision) return; end -- Create the database update record vDatabaseUpdate = {}; vDatabaseUpdate.mSender = pSender; vDatabaseUpdate.mUserName = pUserName; vDatabaseUpdate.mDatabaseID = pDatabaseID; vDatabaseUpdate.mRevision = pRevision; vDatabaseUpdate.mSinceRevision = pSinceRevision; vDatabaseUpdate.mChanges = {}; vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp(); table.insert(gGroupCalendar_Queue.DatabaseUpdates, vDatabaseUpdate); end function CalendarNetwork_InsertEventUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventID, pCommand) local vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID); if not vDatabaseUpdate then return; end -- Bump the update time vDatabaseUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp(); -- local vChanges = vDatabaseUpdate.mChanges[pRevision]; if not vChanges then vChanges = {}; vDatabaseUpdate.mChanges[pRevision] = vChanges; end -- Reconstruct the change string local vChangeString = "EVT:"..pEventID; for vIndex, vCommand in pCommand do vChangeString = vChangeString.."/"..vCommand.opcode; if vCommand.operandString and vCommand.operandString ~= "" then vChangeString = vChangeString..":"..vCommand.operandString; end end -- Save the string table.insert(vChanges, vChangeString); end function CalendarNetwork_EndDatabaseUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision) local vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true); if not vDatabaseUpdate then return; end -- Sanity check: make sure the sinceRevision field matches the original UPD message if vDatabaseUpdate.mSinceRevision ~= pSinceRevision then return; end -- The update was received successfully. -- Copy the changes to the change list if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_EndDatabaseUpdate: Process update for "..pUserName..","..pDatabaseID..","..pRevision.." since revision "..pSinceRevision.." from "..pSender); end local vDatabase = EventDatabase_GetDatabase(pUserName, true); local vDatabaseChanges = vDatabase.Changes; local vReconstructDatabase = false; -- If the update is for one of our own databases process it -- separately if vDatabase.IsPlayerOwned then CalendarNetwork_AskProcessSelfUpdate(vDatabaseUpdate); return; end -- Process the update CalendarNetwork_ProcessDatabaseUpdate(vDatabaseUpdate, false); end function CalendarNetwork_ProcessDatabaseUpdate(pDatabaseUpdate, pForceReconstruct) local vIsOwnerUpdate = pDatabaseUpdate.mSender == pDatabaseUpdate.mUserName; local vDatabase = EventDatabase_GetDatabase(pDatabaseUpdate.mUserName, true); local vDatabaseChanges = vDatabase.Changes; local vReconstructDatabase = pForceReconstruct; if not vDatabaseChanges or vDatabaseChanges.ID ~= pDatabaseUpdate.mDatabaseID then vDatabase.Changes = CalendarChanges_New(pDatabaseUpdate.mDatabaseID); vDatabaseChanges = vDatabase.Changes; end for vRevision = pDatabaseUpdate.mSinceRevision + 1, pDatabaseUpdate.mRevision do local vChanges = pDatabaseUpdate.mChanges[vRevision]; -- If the revision is newer than what we have, insert the new data if vRevision > vDatabaseChanges.Revision then CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges); if vChanges then EventDatabase_ExecuteChangeList(vDatabase, vChanges, true); end -- If the revision overlaps what we have and the update is from -- the owner, then compare the data to make sure it's intact. If -- it doesn't match then update the changes and flag the database -- for reconstruction elseif vIsOwnerUpdate then -- If we're not reconstructing then compare the owner's changes to what -- we've gotten before and see if they match. Switch to reconstruction -- mode if there's a discrepancy if not vReconstructDatabase then local vChangeList = CalendarChanges_GetChangeList(vDatabaseChanges, vRevision); if ((vChanges ~= nil) ~= (vChangeList ~= nil)) or (vChangeList ~= nil and table.getn(vChangeList) ~= table.getn(vChanges)) then vReconstructDatabase = true; if gGroupCalendar_Settings.DebugReconstruct then Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because changes for revision "..vRevision.." are different lengths"); end elseif vChanges ~= nil then for vChangeIndex, vChange in vChanges do local vOldChange = vChangeList[vChangeIndex]; if vOldChange ~= vChange then vReconstructDatabase = true; if gGroupCalendar_Settings.DebugReconstruct then Calendar_DebugMessage("Reconstructing "..vDatabase.UserName.." because change "..vChangeIndex.." for revision "..vRevision.." doesn't match"); Calendar_DebugMessage("Previously: "..vOldChange); Calendar_DebugMessage("Now: "..vChange); end break; end end end end -- Just copy the changes over if we're in re-construction mode if vReconstructDatabase then CalendarChanges_SetChangeList(vDatabaseChanges, vRevision, vChanges); end end end -- Make sure the current revision stamp matches the update vDatabaseChanges.Revision = pDatabaseUpdate.mRevision; -- Update AuthRevision if the update came from the owner if vIsOwnerUpdate and pDatabaseUpdate.mSinceRevision <= vDatabaseChanges.AuthRevision then vDatabaseChanges.AuthRevision = pDatabaseUpdate.mRevision; end if vReconstructDatabase then EventDatabase_ReconstructDatabase(vDatabase); else GroupCalendar_MajorDatabaseChange(vDatabase); end end StaticPopupDialogs["CONFIRM_CALENDAR_SELF_UPDATE"] = { text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg), button1 = TEXT(GroupCalendar_cUpdate), button2 = TEXT(CANCEL), OnAccept = function() CalendarNetwork_ProcessSelfUpdate(); end, OnCancel = function() CalendarNetwork_RejectSelfUpdate(); end, timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1, }; gGroupCalendar_DidAskSelfUpdate = false; gGroupCalendar_SelfDatabaseUpdate = nil; function CalendarNetwork_AskProcessSelfUpdate(pDatabaseUpdate) -- Only ask once if gGroupCalendar_DidAskSelfUpdate then return; end gGroupCalendar_DidAskSelfUpdate = true; gGroupCalendar_SelfDatabaseUpdate = pDatabaseUpdate; -- Format the message local vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfUpdateParamFormat, pDatabaseUpdate); -- Show the dialog StaticPopup_Show("CONFIRM_CALENDAR_SELF_UPDATE", vMessage); end StaticPopupDialogs["CONFIRM_CALENDAR_SELF_RSVP_UPDATE"] = { text = TEXT(GroupCalendar_cConfirmSelfUpdateMsg), button1 = TEXT(GroupCalendar_cUpdate), button2 = TEXT(CANCEL), OnAccept = function() CalendarNetwork_ProcessSelfRSVPUpdate(); end, OnCancel = function() CalendarNetwork_RejectSelfRSVPUpdate(); end, timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1, }; gGroupCalendar_DidAskSelfRSVPUpdate = false; gGroupCalendar_SelfRSVPUpdate = nil; function CalendarNetwork_AskProcessSelfRSVPUpdate(pRSVPUpdate) -- Only ask once if gGroupCalendar_DidAskSelfRSVPUpdate then return; end gGroupCalendar_DidAskSelfRSVPUpdate = true; gGroupCalendar_SelfRSVPUpdate = pRSVPUpdate; -- Format the message local vMessage = Calendar_FormatNamed(GroupCalendar_cConfirmSelfRSVPUpdateParamFormat, pRSVPUpdate); -- Show the dialog StaticPopup_Show("CONFIRM_CALENDAR_SELF_RSVP_UPDATE", vMessage); end function CalendarNetwork_ProcessSelfUpdate() CalendarNetwork_ProcessDatabaseUpdate(gGroupCalendar_SelfDatabaseUpdate, true); end function CalendarNetwork_RejectSelfUpdate() local vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfDatabaseUpdate.mUserName, true); if not vDatabase then return; end EventDatabase_RebuildDatabase(vDatabase); end function CalendarNetwork_CancelDatabaseUpdate(pSender, pUserName, pDatabaseID) CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, true); end function CalendarNetwork_FindOpenEventChangeRecord(pChanges, pEventID) for vIndex, vChange in pChanges do if vChange.mEventID == pEventID and vChange.mOpen then return vChange; end end return nil; end function CalendarNetwork_KillSendersDatabaseUpdates(pSender) local vDatabaseUpdate = CalendarNetwork_FindDatabaseUpdate(pSender, nil, nil, true); if not vDatabaseUpdate or vDatabaseUpdate.mUserName == pDontRequestForUserName then return; end CalendarNetwork_RequestUpdateForUser(vDatabaseUpdate.mUserName, "DB"); end function CalendarNetwork_FindDatabaseUpdate(pSender, pUserName, pDatabaseID, pDelete) for vIndex, vDatabaseUpdate in gGroupCalendar_Queue.DatabaseUpdates do if (pSender == nil or vDatabaseUpdate.mSender == pSender) and (pUserName == nil or vDatabaseUpdate.mUserName == pUserName) and (pDatabaseID == nil or vDatabaseUpdate.mDatabaseID == pDatabaseID) then -- Delete the update if requested if pDelete then table.remove(gGroupCalendar_Queue.DatabaseUpdates, vIndex); end return vDatabaseUpdate; end end return nil; end function CalendarNetwork_DeleteRSVPs(pSender, pUserName, pDatabaseID, pRevision) -- Get the database local vDatabase = EventDatabase_GetDatabase(pUserName, false); -- Nothing to do if we don't even have the database if not vDatabase then return; end -- Bail out if our stuff is better if not CalendarNetwork_UpdateIsBetterThanOurs(pSender, pUserName, pDatabaseID, pRevision, vDatabase, vDatabase.RSVPs) then return; end -- Empty the specified changelist EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID); vDatabase.RSVPs.Revision = pRevision; end function CalendarNetwork_BeginRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision) -- If the same sender has a pending update already in progress, kill it CalendarNetwork_KillSendersRSVPUpdates(pSender, pUserName); -- Get the database local vDatabase = EventDatabase_GetDatabase(pUserName, false); -- Don't listen to updates for our own databases if vDatabase and vDatabase.IsPlayerOwned and not gGroupCalendar_Settings.AllowSelfUpdate then return; end local vChanges; if vDatabase then vChanges = vDatabase.RSVPs; else vChanges = nil; end -- Ignore the update if it isn't from the owner and -- our ID is newer if string.lower(pSender) ~= string.lower(pUserName) and vChanges then if (vChanges.ID > pDatabaseID) or (vChanges.ID == pDatabaseID and vChanges.Revision >= pRevision) then return; end end -- If it's from the owner and isn't the same revision as ours, -- then delete the database. If the update is from revision -- zero go ahead with the update otherwise ignore it and request -- and update from zero if string.lower(pSender) == string.lower(pUserName) and vChanges and vChanges.ID ~= pDatabaseID then EventDatabase_PurgeRSVPs(vDatabase, pDatabaseID); if pSinceRevision ~= 0 then -- Request an update and exit CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, 0) return; end end -- See if there's another update for the same database already in progress local vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(nil, pUserName, nil); if vRSVPUpdate then -- If the new update is from the owner or -- the old one isn't from the owner and is a higher revision, -- then cancel the old update if string.lower(pSender) == string.lower(pUserName) or (vRSVPUpdate.mSender ~= vRSVPUpdate.mUserName and CalendarNetwork_DatabaseIsNewer(vRSVPUpdate.mDatabaseID, vRSVPUpdate.mRevision, pDatabaseID, pRevision)) then CalendarNetwork_CancelRSVPUpdate(nil, pUserName, nil); -- Otherwise cancel this one else return; end end if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_BeginRSVPUpdate: "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender); end -- Can't accept updates which don't cover the last revision we received if vChanges and pSinceRevision > vChanges.Revision then -- Ask for another copy of the update starting with the revision we need CalendarNetwork_QueueRFURequest(pUserName, "RAT", pDatabaseID, vChanges.Revision); return; end -- Create the update record vRSVPUpdate = {}; vRSVPUpdate.mSender = pSender; vRSVPUpdate.mUserName = pUserName; vRSVPUpdate.mDatabaseID = pDatabaseID; vRSVPUpdate.mRevision = pRevision; vRSVPUpdate.mSinceRevision = pSinceRevision; vRSVPUpdate.mChanges = {}; vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp(); table.insert(gGroupCalendar_Queue.RSVPUpdates, vRSVPUpdate); end function CalendarNetwork_InsertRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pEventFields) local vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID); if not vRSVPUpdate then if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_InsertRSVPUpdate: Ignoring update for "..pUserName..","..pDatabaseID..": Update not found"); end return; end -- Bump the update time vRSVPUpdate.mLastMessageTime = Calendar_GetCurrentLocalDateTimeStamp(); -- local vChanges = vRSVPUpdate.mChanges[pRevision]; if not vChanges then vChanges = {}; vRSVPUpdate.mChanges[pRevision] = vChanges; end -- Process the event command local vChangeString = "EVT:"..pEventFields; table.insert(vChanges, vChangeString); end function CalendarNetwork_EndRSVPUpdate(pSender, pUserName, pDatabaseID, pRevision, pSinceRevision) local vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true); if not vRSVPUpdate then return; end -- Sanity check: make sure the sinceRevision field matches the original UPD message if vRSVPUpdate.mSinceRevision ~= pSinceRevision then return; end -- The update was received successfully. -- Process the commands in the update if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Process update for "..pUserName..","..pRevision.." since revision "..pSinceRevision.." from "..pSender); end local vDatabase = EventDatabase_GetDatabase(pUserName, true); local vRSVPChanges = vDatabase.RSVPs; local vReconstructRSVPs = false; -- If the update is for one of our own databases process it -- separately if vDatabase.IsPlayerOwned then CalendarNetwork_AskProcessSelfRSVPUpdate(vRSVPUpdate); return; end -- Process the update CalendarNetwork_ProcessRSVPUpdate(vRSVPUpdate, false); end function CalendarNetwork_ProcessRSVPUpdate(pRSVPUpdate, pForceReconstruct) local vIsOwnerUpdate = pRSVPUpdate.mSender == pRSVPUpdate.mUserName; local vDatabase = EventDatabase_GetDatabase(pRSVPUpdate.mUserName, true); local vRSVPChanges = vDatabase.RSVPs; if not vRSVPChanges or vRSVPChanges.ID ~= pRSVPUpdate.mDatabaseID then vDatabase.RSVPs = CalendarChanges_New(pRSVPUpdate.mDatabaseID); vRSVPChanges = vDatabase.RSVPs; end for vRevision = pRSVPUpdate.mSinceRevision + 1, pRSVPUpdate.mRevision do local vChanges = pRSVPUpdate.mChanges[vRevision]; -- If the revision is newer than what we have, insert the new data if vRevision > vRSVPChanges.Revision then CalendarChanges_SetChangeList(vRSVPChanges, vRevision, vChanges); CalendarChanges_Close(vRSVPChanges, vRevision); if vChanges then EventDatabase_ExecuteRSVPChangeList(vDatabase, vChanges, true); end elseif vIsOwnerUpdate and vRevision > vRSVPChanges.AuthRevision then if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring owner update for "..vRevision..": Not implemented"); end else if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("CalendarNetwork_EndRSVPUpdate: Ignoring revision "..vRevision..": Already exists"); end end end vRSVPChanges.Revision = pRSVPUpdate.mRevision; -- Update AuthRevision if the update came from the owner if vIsOwnerUpdate and pRSVPUpdate.mSinceRevision <= vRSVPChanges.AuthRevision then vRSVPChanges.AuthRevision = pRSVPUpdate.mRevision; end end function CalendarNetwork_ProcessSelfRSVPUpdate() CalendarNetwork_ProcessRSVPUpdate(gGroupCalendar_SelfRSVPUpdate, true); end function CalendarNetwork_RejectSelfRSVPUpdate() local vDatabase = EventDatabase_GetDatabase(gGroupCalendar_SelfRSVPUpdate.mUserName, true); if not vDatabase then return; end EventDatabase_RebuildRSVPs(vDatabase); end function CalendarNetwork_CancelRSVPUpdate(pSender, pUserName, pDatabaseID) CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, true); end function CalendarNetwork_KillSendersRSVPUpdates(pSender, pDontRequestForUserName) local vRSVPUpdate = CalendarNetwork_FindRSVPUpdate(pSender, nil, nil, pDelete); if not vRSVPUpdate or vRSVPUpdate.mUserName == pDontRequestForUserName then return; end CalendarNetwork_RequestUpdateForUser(vRSVPUpdate.mUserName, "RAT"); end function CalendarNetwork_FindRSVPUpdate(pSender, pUserName, pDatabaseID, pDelete) for vIndex, vRSVPUpdate in gGroupCalendar_Queue.RSVPUpdates do if (pSender == nil or vRSVPUpdate.mSender == pSender) and (pUserName == nil or vRSVPUpdate.mUserName == pUserName) and (pDatabaseID == nil or vRSVPUpdate.mDatabaseID == pDatabaseID) then -- Delete the update if requested if pDelete then table.remove(gGroupCalendar_Queue.RSVPUpdates,vIndex); end return vRSVPUpdate; end end return nil; end function CalendarNetwork_KillOldUpdates(pDatabaseTag, pUpdates, pMinimumTime) local vIndex = 1; local vNumUpdates = table.getn(pUpdates); while vIndex <= vNumUpdates do local vUpdate = pUpdates[vIndex]; if vUpdate.mLastMessageTime < pMinimumTime then table.remove(pUpdates, vIndex); vNumUpdates = vNumUpdates - 1; -- Re-request the update CalendarNetwork_RequestUpdateForUser(vUpdate.mUserName, pDatabaseTag, false); else vIndex = vIndex + 1; end end end function CalendarNetwork_ParseCommandString(pCommandString) -- Verify the command begins with the message prefix if strsub(pCommandString, 1, gGroupCalendar_MessagePrefixLength) ~= gGroupCalendar_MessagePrefix then return nil; end local vCommandString = strsub(pCommandString, gGroupCalendar_MessagePrefixLength); return CalendarNetwork_ParseCommandSubString(vCommandString) end function CalendarNetwork_ParseCommandSubString(pCommandString) -- Break the command into parts local vCommand = {}; for vOpcode, vOperands in string.gfind(pCommandString, "/(%w+):*([^/]*)") do local vOperation = {}; vOperation.opcode = vOpcode; vOperation.operandString = vOperands; vOperation.operands = CalendarNetwork_ParseParameterString(vOperands); table.insert(vCommand, vOperation); end return vCommand; end function CalendarNetwork_ParseParameterString(pParameterString) local vParameters = {}; local vIndex = 0; local vFound = true; local vStartIndex = 1; while vFound do local vEndIndex; vFound, vEndIndex, vParameter = string.find(pParameterString, "([^,]*),", vStartIndex); vIndex = vIndex + 1; if not vFound then vParameters[vIndex] = string.sub(pParameterString, vStartIndex); break; end vParameters[vIndex] = vParameter; vStartIndex = vEndIndex + 1; end return vParameters; end function CalendarNetwork_NewEvent(pDatabase, pEvent) -- Don't record private events in the change history if pEvent.mPrivate then return; end -- Append a change record for the event local vChangeList = EventDatabase_GetCurrentChangeList(pDatabase); EventDatabase_AppendNewEvent(vChangeList, pEvent, EventDatabase_GetEventPath(pEvent)); end function CalendarNetwork_EventChanged(pDatabase, pEvent, pChangedFields) -- Don't record private events in the change history if pEvent.mPrivate then return; end -- Append a change record for the event local vChangeList = EventDatabase_GetCurrentChangeList(pDatabase); EventDatabase_AppendEventUpdate( vChangeList, pEvent, EventDatabase_GetEventPath(pEvent), pChangedFields); end function CalendarNetwork_RemovingEvent(pDatabase, pEvent) -- Don't record private events in the change history if pEvent.mPrivate then return; end -- Remove any references to the event from the change history EventDatabase_RemoveEventChanges(pDatabase, pEvent); -- Insert a delete event local vChangeList = EventDatabase_GetCurrentChangeList(pDatabase); table.insert(vChangeList, EventDatabase_GetEventPath(pEvent).."DEL"); end function CalendarNetwork_SendRevisionChanged(pChanges, pLabel, pUserName) -- Just leave if there's no channel to communicate on -- or no changes to announce if not gGroupCalendar_Channel.ID or not pChanges then return; end CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."NOU"); end function CalendarNetwork_DBRevisionChanged(pDatabase) CalendarNetwork_SendRevisionChanged(pDatabase.Changes, "DB", pDatabase.UserName); end function CalendarNetwork_RSVPRevisionChanged(pDatabase) CalendarNetwork_SendRevisionChanged(pDatabase.RSVPs, "RAT", pDatabase.UserName); end function CalendarNetwork_RequestAllUpdate() -- Just leave if there's no channel to communicate on if not gGroupCalendar_Channel.ID then return; end -- Send the request immmediately since delaying it for channel silence will only -- increase the number of times that everyone has to transmit their NOU responses CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."ALL/RFU"); end function CalendarNetwork_RequestGuildUpdate(pGuildName, pMinRank) -- Just leave if there's no channel to communicate on if not gGroupCalendar_Channel.ID then return; end -- Send the request immmediately since delaying it for channel silence will only -- increase the number of times that everyone has to transmit their NOU responses if pMinRank then CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName..","..pMinRank.."/RFU"); else CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."GLD:"..pGuildName.."/RFU"); end end function CalendarNetwork_RequestUpdateForUser(pUserName, pDatabaseTag, pRequestImmediately) local vDatabase = EventDatabase_GetDatabase(pUserName); if not vDatabase then return; end local vChanges; if pDatabaseTag == "DB" then vChanges = vDatabase.Changes; elseif pDatabaseTag == "RAT" then vChanges = vDatabase.RSVPs; else Calendar_ErrorMessage("CalendarNetwork_RequestUpdateForUser: Unknown database tag "..pDatabaseTag); return; end if not vChanges then return; end CalendarNetwork_RequestUpdate(vDatabase, vChanges, pDatabaseTag, pRequestImmediately); end function CalendarNetwork_RequestUpdate(pDatabase, pChanges, pDatabaseTag, pRequestImmediately) -- Just leave if there's no channel to communicate on if not gGroupCalendar_Channel.ID then return; end local vID, vRevision; if pChanges then vID = pChanges.ID; vRevision = pChanges.Revision; else vID = 0; vRevision = 0; end if pRequestImmediately then CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pDatabaseTag, pDatabase.UserName, vID, vRevision, 0).."RFU"); else CalendarNetwork_QueueRFURequest(pDatabase.UserName, pDatabaseTag, vID, vRevision); end end function CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName) local vID, vRevision; if pChanges then vID = pChanges.ID; vRevision = pChanges.Revision; else vID = Calendar_GetCurrentDateTimeUT60(); vRevision = 0; end CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, vID, vRevision).."DEL"); end function CalendarNetwork_SendChanges(pChanges, pLabel, pUserName, pLockdown, pSinceRevision) if pLockdown then CalendarChanges_LockdownCurrentChangeList(pChanges); end if CalendarChanges_IsEmpty(pChanges) then CalendarNetwork_SendEmptyChanges(pChanges, pLabel, pUserName); return; end CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."UPD:"..pSinceRevision); for vRevision = pSinceRevision + 1, pChanges.Revision do local vRevisionPath = CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, vRevision); local vChangeList = pChanges.ChangeList[vRevision]; if vChangeList then vChangeList.IsOpen = nil; -- Make sure IsOpen is cleared, a bug may have caused it to remain open for vIndex, vChange in vChangeList do CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..vRevisionPath..vChange); end end end CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..CalendarChanges_GetRevisionPath(pLabel, pUserName, pChanges.ID, pChanges.Revision).."END:"..pSinceRevision); end function CalendarNetwork_QueueOutboundMessage(pMessage) table.insert(gGroupCalendar_Queue.OutboundMessages, pMessage); GroupCalendar_StartUpdateTimer(); end function CalendarNetwork_QueueInboundMessage(pSender, pTrustLevel, pMessage) table.insert(gGroupCalendar_Queue.InboundMessages, {mSender = pSender, mTrustLevel = pTrustLevel, mMessage = pMessage}); if table.getn(gGroupCalendar_Queue.InboundMessages) == 1 then gGroupCalendar_Queue.InboundDelay = 0; end GroupCalendar_StartUpdateTimer(); end function CalendarNetwork_QueueTask(pTaskFunc, pTaskParam, pDelay, pTaskID) local vTask = {mTaskFunc = pTaskFunc, mTaskParam = pTaskParam, mDelay = pDelay, mID = pTaskID}; -- Ignore tasks with duplicate IDs if pTaskID then for vIndex, vTask in gGroupCalendar_Queue.Tasks do if vTask.mID == pTaskID then return; end end end -- Insert the task table.insert(gGroupCalendar_Queue.Tasks, vTask); CalendarNetwork_UpdateTaskQueueDelay(); return true; end function CalendarNetwork_UpdateTaskQueueDelay() local vDelay = nil; for vIndex, vTask in gGroupCalendar_Queue.Tasks do if not vDelay or vTask.mDelay < vDelay then vDelay = vTask.mDelay; end end gGroupCalendar_Queue.TasksDelay = vDelay; if vDelay then GroupCalendar_StartUpdateTimer(); end end function CalendarNetwork_SetTaskDelay(pTaskID, pDelay) -- Ignore tasks with duplicate IDs for vIndex, vTask in gGroupCalendar_Queue.Tasks do if vTask.mID == pTaskID then vTask.mDelay = pDelay; CalendarNetwork_UpdateTaskQueueDelay(); return; end end end function CalendarNetwork_QueueRequest(pRequest, pDelay, pPriority) pRequest.mDelay = pDelay; if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("CalendarNetwork_QueueRequest: "..pRequest.mOpcode.." in "..pDelay.." seconds"); end if not pPriority then pPriority = 2; end table.insert(gGroupCalendar_Queue.Requests[pPriority], pRequest); local vTotalRequests = 0; for vPriority, vRequestQueue in gGroupCalendar_Queue.Requests do vTotalRequests = vTotalRequests + table.getn(gGroupCalendar_Queue.Requests[vPriority]); end if vTotalRequests == 1 or (pRequest.mDelay < gGroupCalendar_Queue.RequestsDelay and pRequest.mDelay < gCalendarNetwork_RequestDelay.RequestQueue) then gGroupCalendar_Queue.RequestsDelay = pRequest.mDelay; end GroupCalendar_StartUpdateTimer(); return true; end function CalendarNetwork_SetRequestPriority(pRequest, pPriority) for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest == pRequest then if vPriority ~= pPriority then table.remove(vRequests, vIndex); table.insert(gGroupCalendar_Queue.Requests[pPriority], vRequest); end return; end end end end function CalendarNetwork_QueueUniqueOpcodeRequest(pRequest, pDelay) -- Remove an existing request with the same opcode for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest.mOpcode == pRequest.mOpcode then table.remove(vRequests, vIndex); break; end end end return CalendarNetwork_QueueRequest(pRequest, pDelay); end function CalendarNetwork_QueueUniqueUserRequest(pRequest, pDelay) -- Remove an existing request with the same opcode and user name for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest.mOpcode == pRequest.mOpcode and vRequest.mUserName == pRequest.mUserName then table.remove(vRequests, vIndex); break; end end end return CalendarNetwork_QueueRequest(pRequest, pDelay); end function CalendarNetwork_ArrayContainsArray(pArray, pSubArray) for vFieldName, vFieldValue in pSubArray do if pArray[vFieldName] ~= vFieldValue then return false; end end return true; end function CalendarNetwork_FindRequest(pRequest) for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if CalendarNetwork_ArrayContainsArray(vRequest, pRequest) then return vRequest; end end end return nil; end function CalendarNetwork_FindUPDRequest(pDatabaseTag, pUserName) return CalendarNetwork_FindRequest({mOpcode = pDatabaseTag.."_UPD", mUserName = pUserName}); end function CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pFromRevision) local vOpcode = pDatabaseTag.."_RFU"; for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest.mOpcode == vOpcode and vRequest.mUserName == pUserName then if vRequest.mDatabaseID == pDatabaseID and pFromRevision <= vRequest.mRevision then if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("Removing redundant RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision); end table.remove(vRequests, vIndex); return false; -- Return false to indicate there is no longer an RFU for this user else if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("Keeping RFU request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision); Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision); end end if gGroupCalendar_Settings.DebugUpdates then Calendar_DebugMessage("RFU for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pFromRevision.." already exists"); end return true; -- Return true to indicate there is an RFU for this user end end end return false; -- Return false to indicate no RFU for this user was found end function CalendarNetwork_NeedsUpdateTimer() if gGroupCalendar_Queue.TasksDelay then return true; end for vPriority, vRequests in gGroupCalendar_Queue.Requests do if table.getn(vRequests) > 0 then return true; end end if table.getn(gGroupCalendar_Queue.InboundMessages) > 0 then return true; end if table.getn(gGroupCalendar_Queue.OutboundMessages) > 0 then return true; end return false; end function CalendarNetwork_ProcessTaskQueue(pElapsed) if not gGroupCalendar_Queue.TasksDelay then return; end gGroupCalendar_Queue.TasksDelay = gGroupCalendar_Queue.TasksDelay - pElapsed; gGroupCalendar_Queue.TasksElapsed = gGroupCalendar_Queue.TasksElapsed + pElapsed; if gGroupCalendar_Queue.TasksDelay <= 0 then local vNumTasks = table.getn(gGroupCalendar_Queue.Tasks); local vIndex = 1; gGroupCalendar_Queue.TasksDelay = nil; local vLatencyStartTime; if gGroupCalendar_Settings.DebugLatency then vLatencyStartTime = GetTime(); end while vIndex <= vNumTasks do local vTask = gGroupCalendar_Queue.Tasks[vIndex]; vTask.mDelay = vTask.mDelay - gGroupCalendar_Queue.TasksElapsed; if vTask.mDelay <= 0 then table.remove(gGroupCalendar_Queue.Tasks, vIndex); vNumTasks = vNumTasks - 1; -- Perform the task vTask.mTaskFunc(vTask.mTaskParam); else if not gGroupCalendar_Queue.TasksDelay or vTask.mDelay < gGroupCalendar_Queue.TasksDelay then gGroupCalendar_Queue.TasksDelay = vTask.mDelay; end vIndex = vIndex + 1; end end if gGroupCalendar_Settings.DebugLatency then local vElapsed = GetTime() - vLatencyStartTime; if vElapsed > 0.1 then Calendar_DebugMessage("Tasks took "..vElapsed.."s to execute ("..vNumTasks.." tasks)"); end end gGroupCalendar_Queue.TasksElapsed = 0; end end function CalendarNetwork_ProcessInboundQueue(pElapsed, pCurrentTimeStamp) local vNumInboundMessages = table.getn(gGroupCalendar_Queue.InboundMessages); if vNumInboundMessages > 0 then local vCollisionDetected = false; gGroupCalendar_Queue.InboundDelay = gGroupCalendar_Queue.InboundDelay - pElapsed; if gGroupCalendar_Queue.InboundDelay <= 0 then -- Process one message local vMessage = gGroupCalendar_Queue.InboundMessages[1]; table.remove(gGroupCalendar_Queue.InboundMessages, 1); local vLatencyStartTime; if gGroupCalendar_Settings.DebugLatency then vLatencyStartTime = GetTime(); end CalendarNetwork_ProcessCommandString(vMessage.mSender, vMessage.mTrustLevel, vMessage.mMessage, pCurrentTimeStamp); if gGroupCalendar_Settings.DebugLatency then local vElapsed = GetTime() - vLatencyStartTime; if vElapsed > 0.1 then Calendar_DebugMessage("Inbound message took "..vElapsed.."s to process"); Calendar_DumpArray("Message", vMessage); end end gGroupCalendar_Queue.InboundDelay = gCalendarNetwork_RequestDelay.InboundQueue; if gGroupCalendar_Queue.InboundLastSender ~= vMessage.mSender then if gGroupCalendar_Queue.InboundTimeSinceLastMessage < gCalendarNetwork_RequestDelay.OutboundQueueGapMin then -- Collision between other senders detected if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("Collision detected between "..gGroupCalendar_Queue.InboundLastSender.." and "..vMessage.mSender); end vCollisionDetected = true; end gGroupCalendar_Queue.InboundLastSender = vMessage.mSender; end gGroupCalendar_Queue.InboundTimeSinceLastMessage = 0; end -- Terminate processing while there are any pending inbound messages and delay further -- processing of the outbound and request queues -- If there was a collision and we *weren't* part of it then increase the range to -- avoid allowing us to become a part of the next collision. This should gradually eliminate -- transmittors from the collisions until someone successfully takes over the wire. -- This probably seems really counterintuitive, but the idea is to eliminate players who -- didn't participate in a collision from attempting to try again. By doing this, each collision -- should eliminate a significant number of players from the accident, leaving only the actual -- victims to try again. The next collision will then eliminate several of those players -- until within a few seconds only one player remains and that player will then have control -- and finish his transmission. local vOutboundQueueGapMin = gCalendarNetwork_RequestDelay.OutboundQueueGapMin; local vRequestQueueGapMin = gCalendarNetwork_RequestDelay.RequestQueueGapMin; if vCollisionDetected and gGroupCalendar_Queue.OutboundTimeSinceLastMessage > gCalendarNetwork_RequestDelay.OutboundQueueGapMin then -- Collision detected between other players so use a longer gap to make sure -- we're not part of the next collision vOutboundQueueGapMin = vOutboundQueueGapMin + gCalendarNetwork_RequestDelay.OutboundQueueGapWidth; vRequestQueueGapMin = vRequestQueueGapMin + gCalendarNetwork_RequestDelay.RequestQueueGapWidth; end vRandom = math.random(); if gGroupCalendar_Queue.OutboundDelay < vOutboundQueueGapMin then gGroupCalendar_Queue.OutboundDelay = vOutboundQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.OutboundQueueGapWidth; end if gGroupCalendar_Queue.RequestsDelay < vRequestQueueGapMin then gGroupCalendar_Queue.RequestsDelay = vRequestQueueGapMin + vRandom * gCalendarNetwork_RequestDelay.RequestQueueGapWidth; end return true; end return false; end function CalendarNetwork_ProcessOutboundQueue(pElapsed) local vNumOutboundMessages = table.getn(gGroupCalendar_Queue.OutboundMessages); if vNumOutboundMessages > 0 then gGroupCalendar_Queue.OutboundDelay = gGroupCalendar_Queue.OutboundDelay - pElapsed; if gGroupCalendar_Queue.OutboundDelay <= 0 then local vLatencyStartTime; if gGroupCalendar_Settings.DebugLatency then vLatencyStartTime = GetTime(); end -- Send one message local vMessage = gGroupCalendar_Queue.OutboundMessages[1]; table.remove(gGroupCalendar_Queue.OutboundMessages, 1); CalendarNetwork_SendMessage(vMessage); gGroupCalendar_Queue.OutboundDelay = gCalendarNetwork_RequestDelay.OutboundQueue; -- Reset the time since last message to figure out if we're part of a collision gGroupCalendar_Queue.OutboundTimeSinceLastMessage = 0; -- if gGroupCalendar_Settings.DebugLatency then local vElapsed = GetTime() - vLatencyStartTime; if vElapsed > 0.1 then Calendar_DebugMessage("Outbound message took "..vElapsed.."s to process"); Calendar_DebugMessage(vMessage); end end -- Stop processing if this isn't the last outbound message, otherwise -- go ahead and allow the request queue to be processed if vNumOutboundMessages == 1 then gGroupCalendar_Queue.RequestsDelay = 0; else return true; end else -- Terminate processing while there are any pending outbound messages and delay further -- processing of the request queues gGroupCalendar_Queue.RequestsDelay = gCalendarNetwork_RequestDelay.RequestQueueGapMin + math.random() * gCalendarNetwork_RequestDelay.RequestQueueGapWidth; return true; end end return false; end GroupCalendar_cUpdateRequestOpcodes = { ["DB_UPD"] = true, ["RAT_UPD"] = true, ["DB_NOU"] = true, ["RAT_NOU"] = true }; function CalendarNetwork_CanProcessRequest(pRequest) if not GroupCalendar_cUpdateRequestOpcodes[pRequest.mOpCode] then return true; end if not gGroupCalendar_EnableUpdates then return false; end if pRequest.mUserName then local vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false); if vDatabase and vDatabase.IsPlayerOwned and not gGroupCalendar_EnableSelfUpdates then return false; end end return true; end function CalendarNetwork_ProcessRequestQueue(pElapsed, pSuppressProcessing) local vNumRequests = 0; for vPriority, vRequests in gGroupCalendar_Queue.Requests do vNumRequests = vNumRequests + table.getn(vRequests); end if vNumRequests > 0 then gGroupCalendar_Queue.RequestsDelay = gGroupCalendar_Queue.RequestsDelay - pElapsed; gGroupCalendar_Queue.RequestsElapsed = gGroupCalendar_Queue.RequestsElapsed + pElapsed; if gGroupCalendar_Queue.RequestsDelay <= 0 then -- Process one request local vDidProcessRequest = pSuppressProcessing; local vMinDelayForNextRequest = nil; local vLatencyStartTime; local vTotalRequests = 0; if gGroupCalendar_Settings.DebugLatency then vLatencyStartTime = GetTime(); end for vPriority, vRequests in gGroupCalendar_Queue.Requests do local vIndex = 1; local vNumRequestsThisQueue = table.getn(vRequests); vTotalRequests = vTotalRequests + vNumRequestsThisQueue; while vIndex <= vNumRequestsThisQueue do local vRequest = vRequests[vIndex]; if vRequest.mDelay > 0 then vRequest.mDelay = vRequest.mDelay - gGroupCalendar_Queue.RequestsElapsed; if vRequest.mDelay < 0 then vRequest.mDelay = 0; end end if vRequest.mDelay == 0 and not vDidProcessRequest then table.remove(vRequests, vIndex); vNumRequestsThisQueue = vNumRequestsThisQueue - 1; CalendarNetwork_ProcessRequest(vRequest); vDidProcessRequest = true; else if not vMinDelayForNextRequest or vRequest.mDelay < vMinDelayForNextRequest then vMinDelayForNextRequest = vRequest.mDelay; end vIndex = vIndex + 1; end end -- while vIndex end -- for vPriority if gGroupCalendar_Settings.DebugLatency then local vElapsed = GetTime() - vLatencyStartTime; if vElapsed > 0.1 then Calendar_DebugMessage("Requests took "..vElapsed.."s to process ("..vTotalRequests.." total requests)"); end end if not vMinDelayForNextRequest or vMinDelayForNextRequest < gCalendarNetwork_RequestDelay.RequestQueue then vMinDelayForNextRequest = gCalendarNetwork_RequestDelay.RequestQueue; end gGroupCalendar_Queue.RequestsDelay = vMinDelayForNextRequest; gGroupCalendar_Queue.RequestsElapsed = 0; end end end function CalendarNetwork_ProcessQueues(pElapsed) -- Get the current time stamp local vCurrentTimeStamp = Calendar_GetCurrentLocalDateTimeStamp(); -- Process tasks CalendarNetwork_ProcessTaskQueue(pElapsed); -- Update the collision detection counters gGroupCalendar_Queue.InboundTimeSinceLastMessage = gGroupCalendar_Queue.InboundTimeSinceLastMessage + pElapsed; gGroupCalendar_Queue.OutboundTimeSinceLastMessage = gGroupCalendar_Queue.OutboundTimeSinceLastMessage + pElapsed; -- Process inbound messages local vSuppressRequests = false; if CalendarNetwork_ProcessInboundQueue(pElapsed, vCurrentTimeStamp) then vSuppressRequests = true; elseif CalendarNetwork_ProcessOutboundQueue(pElapsed) then vSuppressRequests = true; end -- Process pending requests if there are no outbound messages pending CalendarNetwork_ProcessRequestQueue(pElapsed, vSuppressRequests); end function CalendarNetwork_ProcessRequest(pRequest) local vDatabase; if pRequest.mUserName then vDatabase = EventDatabase_GetDatabase(pRequest.mUserName, false); else vDatabase = nil; end if pRequest.mOpcode == "DB_UPD" then if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue CalendarNetwork_SendChanges(vDatabase.Changes, "DB", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision); end elseif pRequest.mOpcode == "RAT_UPD" then if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue CalendarNetwork_SendChanges(vDatabase.RSVPs, "RAT", vDatabase.UserName, vDatabase.IsPlayerOwned, pRequest.mRevision); end elseif pRequest.mOpcode == "DB_NOU" then if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue CalendarNetwork_DBRevisionChanged(vDatabase); end elseif pRequest.mOpcode == "RAT_NOU" then if vDatabase then -- Check for nil since the database may have gotten deleted while in the queue CalendarNetwork_RSVPRevisionChanged(vDatabase); end elseif pRequest.mOpcode == "DB_RFU" then local vCurrentRevision; local vAuthRevision; if vDatabase and vDatabase.Changes then vCurrentRevision = vDatabase.Changes.Revision; vAuthRevision = vDatabase.Changes.AuthRevision; else vCurrentRevision = 0; vAuthRevision = nil; end if vCurrentRevision < pRequest.mRevision or vCurrentRevision == 0 then CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetDBRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU"); end elseif pRequest.mOpcode == "RAT_RFU" then local vCurrentRevision; local vAuthRevision; if vDatabase and vDatabase.RSVPs then vCurrentRevision = vDatabase.RSVPs.Revision; vAuthRevision = vDatabase.RSVPs.AuthRevision; else vCurrentRevision = 0; vAuthRevision = nil; end if vCurrentRevision < pRequest.mRevision or vCurrentRevision == 0 then CalendarNetwork_QueueOutboundMessage(gGroupCalendar_MessagePrefix..EventDatabase_GetRSVPRevisionPath(pRequest.mUserName, pRequest.mDatabaseID, vCurrentRevision, vAuthRevision).."RFU"); end elseif pRequest.mOpcode == "AUTOCONFIG" then CalendarNetwork_DoAutoConfig(pRequest.mCheckDatabaseTrust); elseif pRequest.mOpcode == "DBTRUST" then EventDatabase_CheckDatabaseTrust(); elseif pRequest.mOpcode == "OWNEDNOTICES" then gGroupCalendar_EnableUpdates = true; gGroupCalendar_EnableSelfUpdates = true; CalendarNetwork_SendOwnedDatabaseUpdateNotices(); end end function CalendarNetwork_Initialize() gGroupCalendar_Initialized = true; if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("GroupCalendar Initializing: Starting initialization"); end CalendarNetwork_PlayerGuildChanged(); -- Go ahead and do manual channel configuration now, automatic -- config will be handled once the player guild gets set and -- the roster gets loaded if not gGroupCalendar_PlayerSettings.Channel.AutoConfig then if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("GroupCalendar INIT: Starting up data channel (manual configuration)"); end if gGroupCalendar_PlayerSettings.Channel.Name then CalendarNetwork_SetChannel( gGroupCalendar_PlayerSettings.Channel.Name, gGroupCalendar_PlayerSettings.Channel.Password); else CalendarNetwork_SetChannelStatus("Disconnected"); Calendar_DebugMessage("GroupCalendar channel is not set"); end end Calendar_GetPrimaryTradeskills(); end function CalendarNetwork_SendNotices() -- If trust isn't available yet just defer the notices if not CalendarTrust_TrustCheckingAvailable() then gGroupCalendar_SendNoticesOnRosterUpdate = true; return; end -- if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("Sending notices"); end CalendarNetwork_RequestAllDatabaseUpdates(); -- Request databases for trusted players who haven't sent one yet if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then CalendarNetwork_RequestMissingDatabases(); end -- Schedule a request to send out owned database notices in two minutes CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "OWNEDNOTICES"}, gCalendarNetwork_RequestDelay.OwnedNotices); end function CalendarNetwork_RequestAllDatabaseUpdates() -- Immediately send a notice of our version CalendarNetwork_SendMessage(gGroupCalendar_MessagePrefix.."VER:"..gGroupCalendar_VersionString); -- Immediately request updates to our own databases for vRealmName, vDatabase in gGroupCalendar_Database.Databases do if EventDatabase_DatabaseIsVisible(vDatabase) and vDatabase.IsPlayerOwned then CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", true); CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", true); end end -- Request updates to all other databases after a delay CalendarNetwork_QueueTask(CalendarNetwork_RequestExternalDatabaseUpdates, nil, gCalendarNetwork_RequestDelay.ExternalUpdateRequest, "EXTERNALUPDATE"); end function CalendarNetwork_RequestExternalDatabaseUpdates() if gGroupCalendar_PlayerSettings.Security.TrustAnyone then CalendarNetwork_RequestAllUpdate(); elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies and gGroupCalendar_PlayerGuild then CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank); end if not gGroupCalendar_PlayerSettings.Security.TrustAnyone then for vRealmName, vDatabase in gGroupCalendar_Database.Databases do if EventDatabase_DatabaseIsVisible(vDatabase) then if not vDatabase.IsPlayerOwned and (not gGroupCalendar_PlayerSettings.Security.TrustGuildies or vDatabase.Guild ~= gGroupCalendar_PlayerGuild) then CalendarNetwork_RequestUpdate(vDatabase, vDatabase.Changes, "DB", false); CalendarNetwork_RequestUpdate(vDatabase, vDatabase.RSVPs, "RAT", false); end end end end end function CalendarNetwork_SendOwnedDatabaseUpdateNotices() for vRealmName, vDatabase in gGroupCalendar_Database.Databases do if EventDatabase_DatabaseIsVisible(vDatabase) and vDatabase.IsPlayerOwned then if not CalendarChanges_IsEmpty(vDatabase.Changes) then CalendarNetwork_DBRevisionChanged(vDatabase); end if not CalendarChanges_IsEmpty(vDatabase.RSVPs) then CalendarNetwork_RSVPRevisionChanged(vDatabase); end end end end gCalendarNetwork_GuildMemberRankCache = nil; function CalendarNetwork_FlushCaches() gCalendarNetwork_GuildMemberRankCache = nil; gCalendarNetwork_UserTrustCache = {}; end function CalendarNetwork_GetGuildRosterCache() if gCalendarNetwork_GuildMemberRankCache then return gCalendarNetwork_GuildMemberRankCache; end -- Clear the cache gCalendarNetwork_GuildMemberRankCache = {}; -- Scan the roster and collect the info local vNumGuildMembers = GetNumGuildMembers(true); for vIndex = 1, vNumGuildMembers do local vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(vIndex); if vName then -- Have to check for name in case a guild member gets booted while querying the roster local vMemberInfo = { Name = vName, RankIndex = vRankIndex, Level = vLevel, Class = vClass, Zone = vZone, OfficerNote = vOfficerNote, Online = vOnline }; gCalendarNetwork_GuildMemberRankCache[strupper(vName)] = vMemberInfo; end end -- Dump any cached trust info gCalendarNetwork_UserTrustCache = {}; return gCalendarNetwork_GuildMemberRankCache; end local gGroupCalendar_SentLoadGuildRoster = false; function CalendarNetwork_LoadGuildRosterTask() if IsInGuild() then GuildRoster(); end -- Schedule another task to load the roster again -- in four minutes CalendarNetwork_QueueTask( CalendarNetwork_LoadGuildRosterTask, nil, 240, "GUILDROSTER"); end function CalendarNetwork_LoadGuildRoster() if not IsInGuild() or GetNumGuildMembers() > 0 or gGroupCalendar_SentLoadGuildRoster then return; end gGroupCalendar_SentLoadGuildRoster = true; if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_LoadGuildRoster: Loading"); end CalendarNetwork_LoadGuildRosterTask(); end function CalendarNetwork_UserIsInSameGuild(pUserName) if not IsInGuild() then return false, nil; end -- Build the roster if GetNumGuildMembers() == 0 then CalendarNetwork_LoadGuildRoster(); return false, nil; -- have to return false for now since we don't really know end -- Search for the member local vUpperUserName = strupper(pUserName); local vRosterCache = CalendarNetwork_GetGuildRosterCache(); local vMemberInfo = vRosterCache[vUpperUserName]; if not vMemberInfo then return false, nil; end return true, vMemberInfo.RankIndex; end function CalendarNetwork_SendAllRevisionNotices() -- Use the same delay for all the requests so that the requests will all go out together -- once they start getting processed local vDelay = gCalendarNetwork_RequestDelay.ProxyNOUMin + math.random() * gCalendarNetwork_RequestDelay.ProxyNOURange; local vOwnedDelay = math.random() * gCalendarNetwork_RequestDelay.OwnedNOURange; for vRealmName, vDatabase in gGroupCalendar_Database.Databases do if EventDatabase_DatabaseIsVisible(vDatabase) then local vRequest = {mOpcode = "DB_NOU", mUserName = vDatabase.UserName}; if vDatabase.IsPlayerOwned then CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay); else CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay); end local vRequest = {mOpcode = "RAT_NOU", mUserName = vDatabase.UserName}; if vDatabase.IsPlayerOwned then CalendarNetwork_QueueUniqueUserRequest(vRequest, vOwnedDelay); else CalendarNetwork_QueueUniqueUserRequest(vRequest, vDelay); end end end end function CalendarNetwork_CancelRedundantUPDRequests(pChanges, pDatabaseTag, pUserName, pDatabaseID, pRevision, pSinceRevision) local vOpcode = pDatabaseTag.."_UPD"; for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest.mOpcode == vOpcode and vRequest.mUserName == pUserName then -- Theirs is better if: -- - Their database ID is higher -- - Their database ID is the same and -- - Their fromRevision is lower or the same -- - Their toRevision is higher or the same if pDatabaseID > vRequest.mDatabaseID or (pDatabaseID == vRequest.mDatabaseID and pSinceRevision <= vRequest.mRevision and (not pChanges or pRevision >= pChanges.Revision)) then if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("Removing UPD request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision); end table.remove(vRequests, vIndex); else if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("Keeping UPD request for "..pDatabaseTag.." "..pUserName..","..vRequest.mDatabaseID..","..vRequest.mRevision.." to "..pChanges.Revision); Calendar_DebugMessage("Better thean "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pSinceRevision.." to "..pRevision); end end return; end end -- for vIndex end -- for vPriority end function CalendarNetwork_CancelRedundantNOURequests(pChanges, pDatabaseTag, pSender, pUserName, pDatabaseID, pRevision) local vOpcode = pDatabaseTag.."_NOU"; for vPriority, vRequests in gGroupCalendar_Queue.Requests do for vIndex, vRequest in vRequests do if vRequest.mOpcode == vOpcode and vRequest.mUserName == pUserName then -- Cancel our update if the revision they're advertising is equal -- to or better than ours or if they're the owner local vTheirsIsBetter; if pSender == pUserName then vTheirsIsBetter = true; else vTheirsIsBetter = not pChanges or pDatabaseID > pChanges.ID or (pDatabaseID == pChanges.ID and pRevision >= pChanges.Revision); end if vTheirsIsBetter then if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("Removing NOU request for "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision); end table.remove(vRequests, vIndex); else if gGroupCalendar_Settings.DebugQueues then Calendar_DebugMessage("Keeping NOU request for "..pDatabaseTag.." "..pUserName..","..pChanges.ID..","..pChanges.Revision); Calendar_DebugMessage("Better than "..pDatabaseTag.." "..pUserName..","..pDatabaseID..","..pRevision); end end return; end end -- for vIndex end -- for vPriority end function CalendarNetwork_SendMessage(pMessage) -- Just leave if there's no channel to communicate on if not gGroupCalendar_Channel.ID then return; end local vSavedAutoClearAFK = GetCVar("autoClearAFK"); SetCVar("autoClearAFK", 0); SendChatMessage(Calendar_EscapeChatString(pMessage), "CHANNEL", nil, gGroupCalendar_Channel.ID); SetCVar("autoClearAFK", vSavedAutoClearAFK); end function CalendarNetwork_ChannelMessageReceived(pSender, pMessage) local vTrustLevel = CalendarTrust_GetUserTrustLevel(pSender); if vTrustLevel > 0 then CalendarNetwork_QueueInboundMessage(pSender, vTrustLevel, pMessage); else if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("ChannelMessageReceived: "..pSender.." is not trusted"); end end end function CalendarNetwork_GetGuildMemberIndex(pPlayerName) local vUpperUserName = strupper(pPlayerName); local vNumGuildMembers = GetNumGuildMembers(true); for vIndex = 1, vNumGuildMembers do local vName = GetGuildRosterInfo(vIndex); if strupper(vName) == vUpperUserName then return vIndex; end end return nil; end function CalendarNetwork_SetAutoConfigData(pPlayerName) if not CanEditPublicNote() or not gGroupCalendar_PlayerSettings.Channel.Name then return false; end local vMemberIndex = CalendarNetwork_GetGuildMemberIndex(pPlayerName); if not vMemberIndex then return false; end CalendarNetwork_RemoveAllAutoConfigData(pPlayerName); local vNote = "["..gGroupCalendar_MessagePrefix.."C:"..gGroupCalendar_PlayerSettings.Channel.Name; if gGroupCalendar_PlayerSettings.Channel.Password then vNote = vNote..","..gGroupCalendar_PlayerSettings.Channel.Password end -- Add the trust group local vTrustGroup = CalendarTrust_GetCurrentTrustGroup(); vNote = vNote.."/T:"..vTrustGroup; -- Add the trust rank if gGroupCalendar_PlayerSettings.Security.TrustGuildies and gGroupCalendar_PlayerSettings.Security.MinTrustedRank then vNote = vNote..","..gGroupCalendar_PlayerSettings.Security.MinTrustedRank; end vNote = vNote.."]"; if gGroupCalendar_Settings.DebugConfig then Calendar_DebugMessage("Setting auto-config data on player "..pPlayerName); end GuildRosterSetPublicNote(vMemberIndex, vNote); return true; end function CalendarNetwork_RemoveAllAutoConfigData(pExcludePlayerName) -- Remove it from the GuildInfoText local vGuildInfoText = GetGuildInfoText(); local vStartIndex, vEndIndex = string.find(vGuildInfoText, "%["..gGroupCalendar_MessagePrefix.."[^%]]+%]"); if vStartIndex then vGuildInfoText = string.sub(vGuildInfoText, 1, vStartIndex - 1)..string.sub(vGuildInfoText, vEndIndex + 1); SetGuildInfoText(vGuildInfoText); end -- Remove it from player notes local vIndex = CalendarNetwork_FindAutoConfigData(nil, pExcludePlayerName); while vIndex do CalendarNetwork_RemoveAutoConfigData(vIndex); vIndex = CalendarNetwork_FindAutoConfigData(vIndex + 1, pExcludePlayerName); end end function CalendarNetwork_RemoveAutoConfigData(pMemberIndex) if not pMemberIndex then Calendar_DebugMessage("CalendarNetwork_RemoveAutoConfigData: nil index"); return; end local vName, vNote = CalendarNetwork_GetGuildPublicNote(pMemberIndex); if not vNote then return false; end if gGroupCalendar_Settings.DebugConfig then Calendar_DebugMessage("Removing auto-config data from player "..vName); end local vPatternString = "%["..gGroupCalendar_MessagePrefix0.."[^%]]+%]"; vNote = string.gsub(vNote, vPatternString, ""); GuildRosterSetPublicNote(pMemberIndex, vNote); return true; end function CalendarNetwork_GetGuildPublicNote(pMemberIndex) local vName, vRank, vRankIndex, vLevel, vClass, vZone, vNote, vOfficerNote, vOnline = GetGuildRosterInfo(pMemberIndex); return vName, vNote; end function CalendarNetwork_GetAutoConfigData() local vGuildInfoText = GetGuildInfoText(); local vConfigSource = GroupCalendar_cGuildInfoConfigSource; local vStartIndex, vEndIndex, vConfigString = string.find(vGuildInfoText, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]"); if not vStartIndex then local vIndex = CalendarNetwork_FindAutoConfigData(); if not vIndex then return nil; end local vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex); if not vNote then return nil; end vStartIndex, vEndIndex, vConfigString = string.find(vNote, "%[("..gGroupCalendar_MessagePrefix.."[^%]]+)%]"); if not vStartIndex then return nil; end vConfigSource = vName; end return CalendarNetwork_ParseCommandString(vConfigString), vConfigSource; end function CalendarNetwork_FindAutoConfigData(pStartingIndex, pExcludePlayerName) -- Build the roster if GetNumGuildMembers() == 0 then CalendarNetwork_LoadGuildRoster(); return nil; end -- Search for the member local vNumGuildMembers = GetNumGuildMembers(true); local vStartingIndex; if pStartingIndex then vStartingIndex = pStartingIndex; else vStartingIndex = 1; end for vIndex = vStartingIndex, vNumGuildMembers do local vName, vNote = CalendarNetwork_GetGuildPublicNote(vIndex); if vNote and (not pExcludePlayerName or string.lower(vName) ~= string.lower(pExcludePlayerName)) and strfind(vNote, "%["..gGroupCalendar_MessagePrefix0) then if gGroupCalendar_Settings.DebugConfig then Calendar_DebugMessage("Found auto-config data on player "..vName); end return vIndex, vName; end end return nil; end function CalendarNetwork_ScheduleCheckDatabaseTrust(pDelay) CalendarNetwork_QueueUniqueOpcodeRequest({mOpcode = "DBTRUST"}, pDelay); end function CalendarNetwork_ScheduleAutoConfig(pDelay, pCheckDatabaseTrust) if gGroupCalendar_Channel.Disconnected then return; end local vRequest = CalendarNetwork_FindRequest({mOpcode = "AUTOCONFIG"}); if vRequest then if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Rescheduling existing request"); end if pDelay < vRequest.mDelay then vRequest.mDelay = pDelay; end if pCheckDatabaseTrust then vRequest.mCheckDatabaseTrust = true; end else if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_ScheduleAutoConfig: Scheduling request"); end CalendarNetwork_QueueRequest({mOpcode = "AUTOCONFIG", mCheckDatabaseTrust = pCheckDatabaseTrust}, pDelay); end end function CalendarNetwork_DoAutoConfig(pCheckDatabaseTrust) if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_DoAutoConfig: Performing auto-config"); end local vCommand, vConfigSource = CalendarNetwork_GetAutoConfigData(); if not vCommand then CalendarNetwork_SetChannelStatus("Error", GroupCalendar_cAutoConfigNotFound); return false; end local vChannel = nil; local vPassword = nil; local vMinTrustedRank = nil; local vTrustGroup = nil; while vCommand[1] ~= nil do local vOpcode = vCommand[1].opcode; local vOperands = vCommand[1].operands; table.remove(vCommand, 1); if vOpcode == "CHN" or vOpcode == "C" then vChannel = vOperands[1]; vPassword = vOperands[2]; elseif vOpcode == "RNK" then vMinTrustedRank = tonumber(vOperands[1]); elseif vOpcode == "T" then vTrustGroup = tonumber(vOperands[1]); vMinTrustedRank = tonumber(vOperands[2]); end end local vAutoPlayerChanged = gGroupCalendar_Channel.AutoPlayer ~= vConfigSource; gGroupCalendar_Channel.AutoPlayer = vConfigSource; if vAutoPlayerChanged then GroupCalendar_ChannelChanged(); -- Send out a change notice just so the calendar knows the player changed end CalendarNetwork_SetChannel(vChannel, vPassword); -- Update the trust settings local vTrustChanged = false; local vCurrentTrustGroup = CalendarTrust_GetCurrentTrustGroup(); if vTrustGroup ~= nil then if vCurrentTrustGroup ~= vTrustGroup then vTrustChanged = true; end else vTrustGroup = vCurrentTrustGroup; end if vMinTrustedRank ~= nil then if gGroupCalendar_PlayerSettings.Security.MinTrustedRank ~= vMinTrustedRank then vTrustChanged = true; end else vMinTrustedRank = gGroupCalendar_PlayerSettings.Security.MinTrustedRank; end if vTrustChanged then CalendarTrust_SetCurrentTrustGroup(vTrustGroup, vMinTrustedRank); elseif pCheckDatabaseTrust then EventDatabase_CheckDatabaseTrust(); end return true; end function CalendarTrust_GetCurrentTrustGroup() if gGroupCalendar_PlayerSettings.Security.TrustAnyone then return 2; elseif gGroupCalendar_PlayerSettings.Security.TrustGuildies then return 1; else return 0; end end function CalendarTrust_SetCurrentTrustGroup(pTrustGroup, pMinRank) if pTrustGroup == 2 then gGroupCalendar_PlayerSettings.Security.TrustAnyone = true; gGroupCalendar_PlayerSettings.Security.TrustGuildies = false; elseif pTrustGroup == 1 then gGroupCalendar_PlayerSettings.Security.TrustAnyone = false; gGroupCalendar_PlayerSettings.Security.TrustGuildies = true; else gGroupCalendar_PlayerSettings.Security.TrustAnyone = false; gGroupCalendar_PlayerSettings.Security.TrustGuildies = false; end gGroupCalendar_PlayerSettings.Security.MinTrustedRank = pMinRank; CalendarTrust_TrustSettingsChanged(); end function CalendarTrust_TrustSettingsChanged() CalendarNetwork_FlushCaches(); EventDatabase_CheckDatabaseTrust(); -- Delete databases owned by players we no longer trust CalendarNetwork_RequestMissingDatabases(); -- Request databases for newly trusted players if gGroupCalendar_PlayerSettings.Security.TrustAnyone then CalendarNetwork_RequestAllUpdate(); else if gGroupCalendar_PlayerSettings.Security.TrustGuildies and gGroupCalendar_PlayerGuild then CalendarNetwork_RequestGuildUpdate(gGroupCalendar_PlayerGuild, gGroupCalendar_PlayerSettings.Security.MinTrustedRank); end end CalendarNetwork_SendAllRevisionNotices(); -- Send out revision notices since trusted players may want to know now end function CalendarTrust_TrustCheckingAvailable() if not gGroupCalendar_PlayerSettings.Security.TrustGuildies then return true; end -- Doesn't matter if they're not in a guild if not IsInGuild() then return true; end -- If trust is guild members only then verify that the roster has been loaded return GetNumGuildMembers() > 0; end function CalendarTrust_GetUserTrustLevel(pUserName) local vUserTrustInfo = gCalendarNetwork_UserTrustCache[pUserName]; if not vUserTrustInfo then vUserTrustInfo = {}; vUserTrustInfo.mTrustLevel = CalendarTrust_CalcUserTrust(pUserName); gCalendarNetwork_UserTrustCache[pUserName] = vUserTrustInfo; end return vUserTrustInfo.mTrustLevel; end function CalendarTrust_UserIsTrusted(pUserName) return CalendarTrust_GetUserTrustLevel(pUserName) == 2; end function CalendarTrust_UserIsTrustedForRSVPs(pUserName) return CalendarTrust_GetUserTrustLevel(pUserName) >= 1; end function CalendarTrust_CalcUserTrust(pUserName) -- If the user is one of our own characters, then trust them completely local vDatabase = EventDatabase_GetDatabase(pUserName, false); if vDatabase and vDatabase.IsPlayerOwned then if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Implicit trust for "..pUserName); end return 2; end local vPlayerSecurity = gGroupCalendar_PlayerSettings.Security.Player[pUserName]; -- See if they're explicity allowed/forbidden if vPlayerSecurity ~= nil then if vPlayerSecurity == 1 then -- Trusted if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: Explicit trust for "..pUserName); end return 2; elseif vPlayerSecurity == 2 then -- Excluded if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." explicity excluded"); end return 0; else Calendar_DebugMessage("GroupCalendar: Unknown player security setting of "..vPlayerSecurity.." for "..pUserName); end end -- Return true if we'll allow anyone in the channel if gGroupCalendar_PlayerSettings.Security.TrustAnyone then if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (all trusted)"); end return 2; end -- Return true if they're in the same guild and of sufficient rank if gGroupCalendar_PlayerSettings.Security.TrustGuildies then local vIsInGuild, vGuildRank = CalendarNetwork_UserIsInSameGuild(pUserName); if vIsInGuild then if not gGroupCalendar_PlayerSettings.Security.MinTrustedRank or vGuildRank <= gGroupCalendar_PlayerSettings.Security.MinTrustedRank then if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." trusted (guild member)"); end return 2; else if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." partially trusted (guild member)"); end return 1; end end end -- Failed all tests if gGroupCalendar_Settings.DebugTrust then Calendar_DebugMessage("CalendarTrust_CalcUserTrust: "..pUserName.." not trusted (all tests failed)"); end return 0; end function CalendarTrust_GetNumTrustedPlayers(pTrustSetting) local vNumPlayers = 0; for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do if vPlayerSecurity == pTrustSetting then vNumPlayers = vNumPlayers + 1; end end return vNumPlayers; end function CalendarTrust_GetIndexedTrustedPlayers(pTrustSetting, pIndex) local vPlayerIndex = 1; for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do if vPlayerSecurity == pTrustSetting then if vPlayerIndex == pIndex then return vPlayerName; end vPlayerIndex = vPlayerIndex + 1; end end return nil; end function CalendarNetwork_RequestMissingDatabases() -- For each player we explicitly trust, see if there's a database for -- them yet. If not, request one for vPlayerName, vPlayerSecurity in gGroupCalendar_PlayerSettings.Security.Player do if vPlayerSecurity == 1 then -- Found a trusted player, see if they have a database local vDatabase = EventDatabase_GetDatabase(vPlayerName, false); if not vDatabase then CalendarNetwork_QueueRFURequest(vPlayerName, "DB", 0, 0); CalendarNetwork_QueueRFURequest(vPlayerName, "RAT", 0, 0); end end end return nil; end function CalendarNetwork_QueueRFURequest(pUserName, pDatabaseTag, pDatabaseID, pRevision) if CalendarNetwork_CancelRedundantRFURequest(pDatabaseTag, pUserName, pDatabaseID, pRevision) then return; end local vRequest = { mOpcode = pDatabaseTag.."_RFU", mUserName = pUserName, mDatabaseID = pDatabaseID, mRevision = pRevision, } CalendarNetwork_QueueRequest(vRequest, gCalendarNetwork_RequestDelay.RFUMin + math.random() * gCalendarNetwork_RequestDelay.RFURange); end function CalendarNetwork_PlayerGuildChanged() -- Update the guild in the database if gGroupCalendar_UserDatabase then gGroupCalendar_UserDatabase.Guild = gGroupCalendar_PlayerGuild; end -- Just return if we're not initialized yet if not gGroupCalendar_Initialized then EventDatabase_UpdateGuildRankCache(); return; end -- Clear the roster load flag gGroupCalendar_SentLoadGuildRoster = false; CalendarNetwork_FlushCaches(); -- If the player is unguilded then simply leave the data -- channel if it was auto-configured, flush any databases -- which are no longer trusted and exit if not IsInGuild() then if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("PlayerGuildChanged: Player is now unguilded"); end if gGroupCalendar_PlayerSettings.Channel.AutoConfig then CalendarNetwork_LeaveChannel(); end EventDatabase_CheckDatabaseTrust(); return; end -- The player is in a new guild or has changed guilds, so -- schedule a roster update if necessary if GetNumGuildMembers() > 0 then if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("PlayerGuildChanged: Roster is already loaded, calling GuildRosterChanged()"); end CalendarNetwork_GuildRosterChanged(); end -- Force the roster to reload or to start loading CalendarNetwork_LoadGuildRosterTask(); end function CalendarNetwork_GuildRosterChanged() if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("CalendarNetwork_GuildRosterChanged: Checking changes"); end EventDatabase_UpdateGuildRankCache(); CalendarNetwork_FlushCaches(); if gGroupCalendar_PlayerSettings.Channel.AutoConfig then CalendarNetwork_ScheduleAutoConfig(gCalendarNetwork_RequestDelay.GuildUpdateAutoConfig, true); else if gGroupCalendar_Settings.DebugInit then Calendar_DebugMessage("PlayerGuildChanged: Channel is set for manual config, verifying database trust"); end CalendarNetwork_ScheduleCheckDatabaseTrust(5); end -- Start sending notices now if we were waiting for a roster update if gGroupCalendar_SendNoticesOnRosterUpdate then gGroupCalendar_SendNoticesOnRosterUpdate = nil; CalendarNetwork_SendNotices(); end end function CalendarNetwork_CheckPlayerGuild() local vPlayerGuild; if IsInGuild() then vPlayerGuild, _, gGroupCalendar_PlayerGuildRank = GetGuildInfo("player"); -- Just return if the server is lagging and the guild info -- isn't available yet if not vPlayerGuild then return; end else vPlayerGuild = nil; gGroupCalendar_PlayerGuildRank = nil; end if gGroupCalendar_PlayerGuild ~= vPlayerGuild then gGroupCalendar_PlayerGuild = vPlayerGuild; CalendarNetwork_PlayerGuildChanged(); end end