Skip to content

Commit f4a62c7

Browse files
committed
Cleanup invite timers better. Improve thread safety of pawn loss handlers and use the right notices. Refactor some party internals. /party command for inspecting internal party state in case of desyncs. /finishquesttype for admin testing.
Fix double pawn lost announcement; adjust lost pawn wallet revive handler to be a bit more secure against failure. Additional logging for investigating connection counts. Fix exception thrown when shutting down the server. Prevent Quick Chats from spamming globally; they default to party, plus other nearby players if you're in a hub area. Refactor server list so that it hotloads properly and can handle downed servers without breaking. Fix party state mangling associated with invitations failing due to timeout, rejection, and cancellation. Deprecate naive lobby handling and remove the associated setting. Add catch and logging for missing job info for CharacterCommon. Adjust ClientItemInfo.SubCategory to more cleanly handle items without a subtype. More logging for failures to find pawns by pawnId or slot index.
1 parent f69b3a4 commit f4a62c7

45 files changed

Lines changed: 525 additions & 274 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Arrowgene.Ddon.Cli/Command/ServerCommand.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public CommandResultType Run(CommandParameter parameter)
178178
_webServer.Stop();
179179
_gameServer.Stop();
180180
_loginServer.Stop();
181+
_database.Stop();
181182
return CommandResultType.Completed;
182183
}
183184

@@ -200,6 +201,11 @@ public void Shutdown()
200201
{
201202
_webServer.Stop();
202203
}
204+
205+
if (_database is not null)
206+
{
207+
_database.Stop();
208+
}
203209
}
204210
}
205211
}

Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,12 @@ private void SelectPawns(Character character, DbConnection? connectionIn = null)
330330
// Old DB is in use and new table not populated with required data for character
331331
Logger.Error($"Character: AccountId={character.AccountId}, CharacterId={character.ContentCharacterId}, CommonId={character.CommonId}, PawnCommonId={pawn.CommonId} is missing table entry in 'ddon_orb_gain_extend_param'.");
332332
}
333+
if (pawn.PawnType != PawnType.Main)
334+
{
335+
Logger.Error($"Character: AccountId={character.AccountId}, CharacterId={character.ContentCharacterId}, CommonId={character.CommonId}, PawnCommonId={pawn.CommonId} has invalid pawn type; locally setting pawn type back to Main.");
336+
pawn.PawnType = PawnType.Main;
337+
}
338+
333339
UpdateCharacterExtendedParams(pawn, ownerCharacter: character);
334340
}
335341
}
@@ -359,10 +365,10 @@ public uint GetMaxAugmentAllocation(CharacterCommon character)
359365

360366
public void UpdateCharacterExtendedParams(CharacterCommon characterCommon, bool newCharacter = false, Character ownerCharacter = null)
361367
{
362-
var ExtendedParams = characterCommon.ExtendedParams;
368+
var extendedParams = characterCommon.ExtendedParams;
363369

364370
// There is always an implicit + 1 ring slot plus the extended params value
365-
characterCommon.JewelrySlotNum = (byte)(CharacterManager.DEFAULT_RING_COUNT + ExtendedParams.JewelrySlot);
371+
characterCommon.JewelrySlotNum = (byte)(CharacterManager.DEFAULT_RING_COUNT + extendedParams.JewelrySlot);
366372

367373
/**
368374
* There are two physical attack traits and two magic attack traits in
@@ -371,20 +377,27 @@ public void UpdateCharacterExtendedParams(CharacterCommon characterCommon, bool
371377
* Similar distinction made with magic. The Gain* stats are extra stats
372378
* on top of the iniate stats. These come from armors and BO/HO trees.
373379
*/
374-
characterCommon.StatusInfo.GainAttack = ExtendedParams.Attack;
375-
characterCommon.StatusInfo.GainDefense = ExtendedParams.Defence;
376-
characterCommon.StatusInfo.GainMagicAttack = ExtendedParams.MagicAttack;
377-
characterCommon.StatusInfo.GainMagicDefense = ExtendedParams.MagicDefence;
378-
characterCommon.StatusInfo.GainStamina = ExtendedParams.StaminaMax;
379-
characterCommon.StatusInfo.GainHP = ExtendedParams.HpMax;
380+
characterCommon.StatusInfo.GainAttack = extendedParams.Attack;
381+
characterCommon.StatusInfo.GainDefense = extendedParams.Defence;
382+
characterCommon.StatusInfo.GainMagicAttack = extendedParams.MagicAttack;
383+
characterCommon.StatusInfo.GainMagicDefense = extendedParams.MagicDefence;
384+
characterCommon.StatusInfo.GainStamina = extendedParams.StaminaMax;
385+
characterCommon.StatusInfo.GainHP = extendedParams.HpMax;
380386

381387
/**
382388
* Additional stats can be earned from the S2 and S3 BO/HO orb trees.
383389
* The stat boosts rewarded for this mechanism rewards both stats for all jobs and stats for
384390
* a specific job only. We abuse JobId.None to store the stats for all jobs.
385391
*/
386392
var extendedJobParams = characterCommon.ExtendedJobParams;
387-
JobId jobId = characterCommon.ActiveCharacterJobData.Job;
393+
JobId jobId = characterCommon.ActiveCharacterJobData?.Job ?? JobId.None;
394+
395+
if (jobId == JobId.None)
396+
{
397+
Logger.Error($"Character CommonId {characterCommon.CommonId} has no active job data.");
398+
return;
399+
}
400+
388401
if (characterCommon is Pawn)
389402
{
390403
extendedJobParams = ownerCharacter.ExtendedJobParams;
@@ -505,12 +518,12 @@ private PacketQueue NotifyClientOfCharacterStatus(GameClient client, CharacterCo
505518
PawnPartyMember pawnPartyMember = (PawnPartyMember)partyMember;
506519
if (client.Party != null)
507520
{
508-
client.Party.EnqueueToAll(pawnPartyMember.GetS2CContextGetParty_ContextNtc(), queue);
521+
client.Party.EnqueueToAll(pawnPartyMember.GetPartyContext(), queue);
509522
}
510523
else
511524
{
512525
// This should never be true but if it is, why?
513-
client.Enqueue(pawnPartyMember.GetS2CContextGetParty_ContextNtc(), queue);
526+
client.Enqueue(pawnPartyMember.GetPartyContext(), queue);
514527
}
515528
}
516529

Arrowgene.Ddon.GameServer/Characters/HubManager.cs

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,20 @@ public HashSet<GameClient> GetClientsInHub(StageLayoutId stageId)
3131

3232
public HashSet<GameClient> GetClientsInHub(uint stageId)
3333
{
34-
if (Server.GameSettings.GameServerSettings.NaiveLobbyContextHandling)
34+
HashSet<GameClient> clients = new();
35+
if (!HubMembers.ContainsKey(stageId))
3536
{
36-
return Server.ClientLookup.GetAll().Distinct().ToHashSet();
37+
return clients;
3738
}
38-
else
39+
40+
foreach (GameClient client in Server.ClientLookup.GetAll())
3941
{
40-
HashSet<GameClient> clients = new();
41-
if (!HubMembers.ContainsKey(stageId))
42+
if (client.Character != null && HubMembers[stageId].Contains(client.Character.CharacterId))
4243
{
43-
return clients;
44+
clients.Add(client);
4445
}
45-
46-
foreach (GameClient client in Server.ClientLookup.GetAll())
47-
{
48-
if (client.Character != null && HubMembers[stageId].Contains(client.Character.CharacterId))
49-
{
50-
clients.Add(client);
51-
}
52-
}
53-
return clients;
5446
}
47+
return clients;
5548
}
5649

5750
// The server maintains an authoritative list of who's in each hub stage.
@@ -62,13 +55,6 @@ public HashSet<GameClient> GetClientsInHub(uint stageId)
6255

6356
public void UpdateLobbyContextOnStageChange(GameClient client, uint previousStageId, uint targetStageId)
6457
{
65-
// Fallback to naive method.
66-
if (Server.GameSettings.GameServerSettings.NaiveLobbyContextHandling)
67-
{
68-
NaiveLobbyHandling(client, previousStageId);
69-
return;
70-
}
71-
7258
// Transitions that do not involve a hub stage don't concern us.
7359
if (!HubMembers.ContainsKey(previousStageId) && !HubMembers.ContainsKey(targetStageId))
7460
{
@@ -181,37 +167,5 @@ public void LeaveAllHubs(GameClient client)
181167
hub.Remove(client.Character.CharacterId);
182168
}
183169
}
184-
185-
// A fallback to the existing mechanism; broadcast/gather the entire server on entry.
186-
private void NaiveLobbyHandling(GameClient client, uint sourceStageId)
187-
{
188-
// Designed to only trigger once on joining the server, the only time your StageId == 0.
189-
if (sourceStageId != 0)
190-
{
191-
return;
192-
}
193-
194-
HashSet<GameClient> targetClients = new HashSet<GameClient>();
195-
HashSet<GameClient> gatherClients = new HashSet<GameClient>();
196-
197-
foreach (GameClient otherClient in Server.ClientLookup.GetAll())
198-
{
199-
if (otherClient.Character is null)
200-
{
201-
continue;
202-
}
203-
targetClients.Add(otherClient);
204-
gatherClients.Add(otherClient);
205-
}
206-
207-
if (targetClients.Any())
208-
{
209-
SendContext(client, targetClients);
210-
}
211-
if (gatherClients.Any())
212-
{
213-
GatherContexts(gatherClients, client);
214-
}
215-
}
216170
}
217171
}

Arrowgene.Ddon.GameServer/Chat/ChatManager.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Arrowgene.Ddon.GameServer.Characters;
12
using Arrowgene.Ddon.GameServer.Party;
23
using Arrowgene.Ddon.Server;
34
using Arrowgene.Ddon.Shared.Entity.PacketStructure;
@@ -165,6 +166,31 @@ private void Deliver(GameClient client, ChatResponse response)
165166
switch (response.Type)
166167
{
167168
case LobbyChatMsgType.Say:
169+
// Quick-chats are local/party-shared.
170+
if (response.MessageFlavor > 0)
171+
{
172+
HashSet<GameClient> recipients;
173+
if (StageManager.IsHubArea(client.Character.Stage))
174+
{
175+
recipients = [.. (client.Party?.Clients ?? [])
176+
.Union(
177+
_Server.ClientLookup.GetAll()
178+
.Where(x => x.Character?.StageNo == client.Character?.StageNo)
179+
)];
180+
}
181+
else
182+
{
183+
recipients = [.. client.Party?.Clients ?? []];
184+
}
185+
186+
response.Recipients.AddRange(recipients);
187+
break;
188+
}
189+
else
190+
{
191+
response.Recipients.AddRange(_Server.ClientLookup.GetAll());
192+
break;
193+
}
168194
case LobbyChatMsgType.Shout:
169195
response.Recipients.AddRange(_Server.ClientLookup.GetAll());
170196
break;
@@ -183,7 +209,7 @@ private void Deliver(GameClient client, ChatResponse response)
183209
}
184210

185211
response.Recipients.AddRange(_Server.ClientLookup.GetAll().Where(
186-
x => x.Character != null
212+
x => x.Character != null
187213
&& client.Character != null
188214
&& x.Character.ClanId == client.Character.ClanId)
189215
);
@@ -193,7 +219,7 @@ private void Deliver(GameClient client, ChatResponse response)
193219
default:
194220
response.Recipients.Add(client);
195221
break;
196-
}
222+
}
197223

198224
Send(response);
199225
}

Arrowgene.Ddon.GameServer/DdonGameServer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ ClientConnectionChangeArgs connectionChangeEventArgs
174174
= new ClientConnectionChangeArgs(ClientConnectionChangeArgs.EventType.CONNECT, client);
175175
connectionChangeEvent(this, connectionChangeEventArgs);
176176
}
177+
178+
Logger.Info($"ClientLookup, Connection: {ClientLookup.GetAll().Count}");
177179
}
178180

179181
protected override void ClientDisconnected(GameClient client)
@@ -195,6 +197,8 @@ ClientConnectionChangeArgs connectionChangeEventArgs
195197
= new ClientConnectionChangeArgs(ClientConnectionChangeArgs.EventType.DISCONNECT, client);
196198
connectionChangeEvent(this, connectionChangeEventArgs);
197199
}
200+
201+
Logger.Info($"ClientLookup, Disconnect: {ClientLookup.GetAll().Count}");
198202
}
199203

200204
public override GameClient NewClient(ITcpSocket socket)

Arrowgene.Ddon.GameServer/Handler/ConnectionReserveServerHandler.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ public override S2CConnectionReserveServerRes Handle(GameClient client, C2SConne
3232
$"The requested server {request.GameServerUniqueID} does not exist.");
3333
}
3434

35+
if (!Server.RpcManager.PingServer(request.GameServerUniqueID))
36+
{
37+
throw new ResponseErrorException(ErrorCode.ERROR_CODE_NET_NOT_CONNECT_GAME_SERVER,
38+
$"The requested server {request.GameServerUniqueID} did not respond when pinged.");
39+
}
40+
3541
var targetServerInfo = Server.RpcManager.ServerListInfo(request.GameServerUniqueID);
3642
if (targetServerInfo.LoginNum >= targetServerInfo.MaxLoginNum)
3743
{

Arrowgene.Ddon.GameServer/Handler/CraftResetCraftpointHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public override S2CCraftResetCraftpointRes Handle(GameClient client, C2SCraftRes
2424
};
2525

2626
Pawn pawn = client.Character.Pawns.Find(p => p.PawnId == request.PawnID)
27-
?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED);
27+
?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED,
28+
$"Couldn't find pawn ID {request.PawnID}.");
2829
pawn.CraftData.CraftPoint = pawn.CraftData.CraftRank - 1;
2930
foreach (CDataPawnCraftSkill skill in pawn.CraftData.PawnCraftSkillList)
3031
{

Arrowgene.Ddon.GameServer/Handler/CraftSkillAnalyzeHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public override S2CCraftSkillAnalyzeRes Handle(GameClient client, C2SCraftSkillA
5959
{
6060
craftSkillAnalyzeRes.AnalyzeResultList.Add(AnalyzeConsumableQuantity(itemInfo, craftPawns));
6161
}
62-
else if (!CraftStartCraftHandler.NoQualitySubCategories.Contains((ItemSubCategory)itemInfo.SubCategory))
62+
else if (!CraftStartCraftHandler.NoQualitySubCategories.Contains(itemInfo.SubCategory))
6363
{
6464
craftSkillAnalyzeRes.AnalyzeResultList.Add(AnalyzeEquipmentQuality(itemInfo, craftPawns));
6565
}

Arrowgene.Ddon.GameServer/Handler/CraftSkillUpHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public override S2CCraftSkillUpRes Handle(GameClient client, C2SCraftSkillUpReq
2525
};
2626

2727
Pawn pawn = client.Character.Pawns.Find(p => p.PawnId == request.PawnID)
28-
?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED);
28+
?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_PAWN_NOT_FOUNDED,
29+
$"Couldn't find pawn ID {request.PawnID}.");
2930

3031
CDataPawnCraftSkill pawnCraftSkill = pawn.CraftData.PawnCraftSkillList.Find(skill => skill.Type == request.SkillType)
3132
?? throw new ResponseErrorException(ErrorCode.ERROR_CODE_CRAFT_INVALID_CRAFT_SKILL_TYPE);

Arrowgene.Ddon.GameServer/Handler/CraftStartCraftHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public override S2CCraftStartCraftRes Handle(GameClient client, C2SCraftStartCra
9898
double calculatedOdds = CraftManager.CalculateEquipmentQualityIncreaseRate(craftPawns);
9999
uint plusValue = 0;
100100
bool isGreatSuccessEquipmentQuality = false;
101-
bool canPlusValue = !itemInfo.SubCategory.HasValue || !NoQualitySubCategories.Contains(itemInfo.SubCategory.Value);
101+
bool canPlusValue = !NoQualitySubCategories.Contains(itemInfo.SubCategory);
102102
if (canPlusValue && !string.IsNullOrEmpty(request.RefineMaterialUID))
103103
{
104104
Item refineMaterialItem = Server.Database.SelectStorageItemByUId(request.RefineMaterialUID, connection);

0 commit comments

Comments
 (0)