Skip to content

Commit

Permalink
enhancement: Handle multiple group edgecases
Browse files Browse the repository at this point in the history
- Created a new class UniqueIdPool to help with creating a pool of
  reusable 32bit IDs.
- Created a new class TimerManager to help with generically using timers
  across the code base.
- Added a timeout to board recruitment.
- Added a timeout to board ready up.
- Added the ability to kick a member from the entry board.
- Added the ability to extend the ready up time.
- Enhanced the vote to abandon content mechanism such that it will
  approve or reject the vote as soon as all members have voted.
- Found the proper NTC to end the group content.
- Synced timers shown in various UI elements so that they closer match
  the actual timer on the server.
- Refactored existing classes to use new UniqueIdPool and TimerManager
  classes.
  • Loading branch information
pacampbell committed Sep 17, 2024
1 parent 3d44060 commit a0f64d2
Show file tree
Hide file tree
Showing 34 changed files with 931 additions and 144 deletions.
188 changes: 165 additions & 23 deletions Arrowgene.Ddon.GameServer/Characters/BoardManager.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Arrowgene.Ddon.GameServer.Utils;
using Arrowgene.Ddon.Server;
using Arrowgene.Ddon.Shared.Entity.PacketStructure;
using Arrowgene.Ddon.Shared.Entity.Structure;
using Arrowgene.Ddon.Shared.Model;
using Arrowgene.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Arrowgene.Ddon.GameServer.Characters
{
Expand All @@ -16,11 +16,13 @@ public class BoardManager
private Dictionary<ulong, Dictionary<uint, GroupData>> _Boards;
private Dictionary<uint, GroupData> _Groups;
private Dictionary<uint, uint> _CharacterToEntryIdMap;
private uint EntryItemIdCounter;
private Stack<uint> _FreeEntryItemIds;
private UniqueIdPool _EntryItemIdPool;

private static readonly ServerLogger Logger = LogProvider.Logger<ServerLogger>(typeof(BoardManager));

public static readonly ushort PARTY_BOARD_TIMEOUT = 3600;
public static readonly ushort ENTRY_BOARD_READY_TIMEOUT = 120;

public class GroupData
{
public ulong BoardId { get; set; }
Expand All @@ -31,6 +33,8 @@ public class GroupData
public Dictionary<uint, bool> MemberReadyState { get; set; }
public bool IsInRecreate { get; set; }
public bool ContentInProgress { get; set; }
public uint RecruitmentTimerId { get; set; }
public uint ReadyUpTimerId { get; set; }

public GroupData()
{
Expand All @@ -55,11 +59,7 @@ public BoardManager(DdonGameServer server)
_Boards = new Dictionary<ulong, Dictionary<uint, GroupData>>();
_Groups = new Dictionary<uint, GroupData>();
_CharacterToEntryIdMap = new Dictionary<uint, uint>();

// Entry ID Tracking
EntryItemIdCounter = 1;
_FreeEntryItemIds = new Stack<uint>();
_FreeEntryItemIds.Push(EntryItemIdCounter);
_EntryItemIdPool = new UniqueIdPool(1);
}

public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam, string password, uint leaderCharacterId)
Expand All @@ -71,7 +71,7 @@ public GroupData CreateNewGroup(ulong boardId, CDataEntryItemParam createParam,
PartyLeaderCharacterId = leaderCharacterId,
};
data.EntryItem.Param = createParam;
data.EntryItem.Id = GenerateEntryItemId();
data.EntryItem.Id = _EntryItemIdPool.GenerateId();

// TODO: Quest Manager look up min/max

Expand Down Expand Up @@ -133,7 +133,17 @@ public bool RemoveGroup(uint entryItemId)
}
}

ReclaimEntryItemId(data.EntryItem.Id);
if (data.RecruitmentTimerId != 0)
{
_Server.TimerManager.CancelTimer(data.RecruitmentTimerId);
}

if (data.ReadyUpTimerId != 0)
{
_Server.TimerManager.CancelTimer(data.ReadyUpTimerId);
}

_EntryItemIdPool.ReclaimId(data.EntryItem.Id);
}

return true;
Expand Down Expand Up @@ -346,28 +356,160 @@ public uint GetEntryItemIdForCharacter(Character character)
return GetEntryItemIdForCharacter(character.CharacterId);
}

private uint GenerateEntryItemId()
public bool StartRecruitmentTimer(uint entryItemId, uint timeoutInSeconds)
{
lock (_Boards)
{
var data = GetGroupData(entryItemId);
if (data == null)
{
return false;
}

data.RecruitmentTimerId = _Server.TimerManager.CreateTimer(timeoutInSeconds, () =>
{
lock (_Boards)
{
foreach (var characterId in data.Members)
{
var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId);
if (memberClient != null)
{
memberClient.Send(new S2CEntryBoardEntryBoardItemLeaveNtc() { LeaveType = EntryBoardLeaveType.EntryBoardTimeUp });
}
}
}
});

if (!_Server.TimerManager.StartTimer(data.RecruitmentTimerId))
{
_Server.TimerManager.CancelTimer(data.RecruitmentTimerId);
return false;
}

return true;
}
}

public ulong GetRecruitmentTimeLeft(uint entryItemId)
{
lock (_Boards)
{
var data = GetGroupData(entryItemId);
if (data == null)
{
return 0;
}
return _Server.TimerManager.GetTimeLeftInSeconds(data.RecruitmentTimerId);
}
}

public bool CancelRecruitmentTimer(uint entryItemId)
{
lock (_Boards)
{
var data = GetGroupData(entryItemId);
if (data == null)
{
return false;
}

_Server.TimerManager.CancelTimer(data.RecruitmentTimerId);
data.RecruitmentTimerId = 0;
return true;
}
}

public bool StartReadyUpTimer(uint entryItemId, uint timeoutInSeconds)
{
lock (_FreeEntryItemIds)
lock (_Boards)
{
if (_FreeEntryItemIds.Count == 0)
var data = GetGroupData(entryItemId);
if (data == null)
{
EntryItemIdCounter = EntryItemIdCounter + 1;
_FreeEntryItemIds.Push(EntryItemIdCounter);
return false;
}

var entryItemId = _FreeEntryItemIds.Pop();
Logger.Info($"Allocating EntryId={entryItemId}");
return entryItemId;
data.ReadyUpTimerId = _Server.TimerManager.CreateTimer(timeoutInSeconds, () =>
{
lock (_Boards)
{
foreach (var characterId in data.Members)
{
var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId);
if (memberClient != null)
{
memberClient.Send(new S2CEntryBoardItemUnreadyNtc());
}
}
// Restart the recruitment timer
data.EntryItem.TimeOut = 3600;
StartRecruitmentTimer(data.EntryItem.Id, 3600);
foreach (var characterId in data.Members)
{
var memberClient = _Server.ClientLookup.GetClientByCharacterId(characterId);
if (memberClient != null)
{
memberClient.Send(new S2CEntryBoardItemTimeoutTimerNtc() { TimeOut = 3600});
}
}
}
});

if (!_Server.TimerManager.StartTimer(data.ReadyUpTimerId))
{
_Server.TimerManager.CancelTimer(data.ReadyUpTimerId);
return false;
}

return true;
}
}

private void ReclaimEntryItemId(uint entryItemId)
public bool CancelReadyUpTimer(uint entryItemId)
{
lock (_FreeEntryItemIds)
lock (_Boards)
{
Logger.Info($"Reclaiming EntryId={entryItemId}");
_FreeEntryItemIds.Push(entryItemId);
var data = GetGroupData(entryItemId);
if (data == null)
{
return false;
}

_Server.TimerManager.CancelTimer(data.ReadyUpTimerId);
data.ReadyUpTimerId = 0;
return true;
}
}

public bool ExtendReadyUpTimer(uint entryItemId)
{
lock (_Boards)
{
var data = GetGroupData(entryItemId);
if (data == null)
{
return false;
}

// UI only allows the count down to start from 120
var ellapsedTime = BoardManager.ENTRY_BOARD_READY_TIMEOUT - _Server.TimerManager.GetTimeLeftInSeconds(data.ReadyUpTimerId);
_Server.TimerManager.ExtendTimer(data.ReadyUpTimerId, (uint)ellapsedTime);
return true;
}
}

public ushort GetTimeLeftToReadyUp(uint entryItemId)
{
lock (_Boards)
{
var data = GetGroupData(entryItemId);
if (data == null)
{
return 0;
}
return (ushort) _Server.TimerManager.GetTimeLeftInSeconds(data.ReadyUpTimerId);
}
}
}
Expand Down
Loading

0 comments on commit a0f64d2

Please sign in to comment.