bots: accept party invitations regardless of level gap
The reset-aware level gate in BotPartyHandler rejected nearly every party invitation on servers with resets, because it folded reset count into the level scale (one reset ~= 400 points against a 500 cap). A veteran player inviting a freshly generated bot was always over the limit, so the invitation was silently declined. Remove the gate so a bot accepts any inviter who is alive and in the world, matching OpenMU's own party action; the situational safeguards (shopping, revenge, mini game, pending invite, human companion) stay in place.
This commit is contained in:
@@ -238,8 +238,10 @@ hunt the leader's maps. The elf heals, the buffs are shared, the party
|
||||
experience bonus applies. Parties re-form every hour.
|
||||
|
||||
A player may invite a bot into their own party: it accepts after a human-like
|
||||
pause of a few seconds, provided the level gap is sane and it is not in the
|
||||
middle of an errand. A living player takes precedence over the bot's own company
|
||||
pause of a few seconds, as long as it is not in the middle of an errand. There is
|
||||
no level gate — just like OpenMU's own party action, a bot accepts an inviter of
|
||||
any level, since it is the player who invites and the bot leaves once it gets
|
||||
bored. A living player takes precedence over the bot's own company
|
||||
— a bot hunting with other bots leaves them for the inviter, and breaks that bot
|
||||
party up if it was leading it, so a player never has to guess which bot happens
|
||||
to be free. In a party the bot follows its leader, defers a due reset, and
|
||||
|
||||
@@ -11,22 +11,13 @@ using MUnique.OpenMU.GameLogic.Offline;
|
||||
/// <see cref="BotMuHelperSettings.AutoAcceptAnyone"/>): the invitation is accepted after a short
|
||||
/// human-like delay, and the bot then follows the leader like any party member (see the follow logic
|
||||
/// in <see cref="BotNavigator"/>) until it gets bored and politely leaves. Safeguards keep it
|
||||
/// believable and abuse-free: no grouping across an absurd level gap, no acceptance while the bot is
|
||||
/// on an errand (shopping trip) or has unfinished business (revenge), and the invitation is
|
||||
/// re-validated when the delay has passed - the inviter may have joined another party or left.
|
||||
/// believable and abuse-free: no acceptance while the bot is on an errand (shopping trip) or has
|
||||
/// unfinished business (revenge), and the invitation is re-validated when the delay has passed - the
|
||||
/// inviter may have joined another party or left. There is no level gate, matching OpenMU's own party
|
||||
/// action: it is the player who invites, and the bot leaves again once it gets bored.
|
||||
/// </summary>
|
||||
internal static class BotPartyHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum difference of the reset-aware effective level (see
|
||||
/// <see cref="BotResetHandler.GetEffectiveLevel"/>) between the bot and the inviter. Within one
|
||||
/// reset worth of levels plus some slack, hunting together still makes sense for both; grouping a
|
||||
/// fresh character with a 15-resets veteran would only be a power-leveling service. On servers
|
||||
/// without the reset feature the plain levels always lie within this bound, matching OpenMU's own
|
||||
/// party action, which has no level gate at all.
|
||||
/// </summary>
|
||||
private const int MaxEffectiveLevelGap = 500;
|
||||
|
||||
/// <summary>Lower bound of the human-like delay before the bot answers an invitation.</summary>
|
||||
private static readonly TimeSpan MinAcceptDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
@@ -67,7 +58,7 @@ internal static class BotPartyHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsRequesterEligible(bot, requester))
|
||||
if (!IsRequesterEligible(requester))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -141,7 +132,7 @@ internal static class BotPartyHandler
|
||||
{
|
||||
// Re-validate: between the invitation and this answer, the bot may have joined a human's party
|
||||
// and the inviter may have died, left the game or joined another party.
|
||||
if (HasHumanCompanion(bot) || !IsRequesterEligible(bot, requester))
|
||||
if (HasHumanCompanion(bot) || !IsRequesterEligible(requester))
|
||||
{
|
||||
bot.Logger.LogInformation("Bot '{Name}' dropped the party invitation of '{Requester}' - the situation changed.", bot.Name, requester.Name);
|
||||
return;
|
||||
@@ -209,14 +200,8 @@ internal static class BotPartyHandler
|
||||
await party.KickMySelfAsync(bot).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool IsRequesterEligible(OfflinePlayer bot, Player requester)
|
||||
private static bool IsRequesterEligible(Player requester)
|
||||
{
|
||||
if (!requester.IsAlive || requester.PlayerState.CurrentState != PlayerState.EnteredWorld)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var levelGap = Math.Abs(BotResetHandler.GetEffectiveLevel(bot) - BotResetHandler.GetEffectiveLevel(requester));
|
||||
return levelGap <= MaxEffectiveLevelGap;
|
||||
return requester.IsAlive && requester.PlayerState.CurrentState == PlayerState.EnteredWorld;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,22 +44,25 @@ public class BotPartyHandlerTest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An inviter whose effective level is too far from the bot's is declined - the group would only
|
||||
/// be a power-leveling service.
|
||||
/// There is no level gate (matching OpenMU's own party action): a bot accepts an inviter of any
|
||||
/// level, since it is the player who invites and the bot leaves again once it gets bored. The
|
||||
/// inviter here is a maxed veteran (character level 400 plus the master level cap of 200, i.e. the
|
||||
/// ceiling of the Season 6 seed) inviting a low-level bot - the widest gap a stock server produces.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async ValueTask RejectsTooLargeLevelGapAsync()
|
||||
public async ValueTask AcceptsInviteRegardlessOfLevelGapAsync()
|
||||
{
|
||||
var gameContext = GameContextTestHelper.CreateGameContext();
|
||||
var bot = await CreateBotAsync(gameContext, "Bot").ConfigureAwait(false);
|
||||
var requester = await CreateHumanAsync(gameContext, "Human").ConfigureAwait(false);
|
||||
requester.Attributes![Stats.Level] = 700;
|
||||
requester.Attributes![Stats.Level] = 400;
|
||||
requester.Attributes![Stats.MasterLevel] = 200;
|
||||
|
||||
var scheduled = await BotPartyHandler.TryScheduleAcceptAsync(bot, requester, TimeSpan.Zero).ConfigureAwait(false);
|
||||
|
||||
Assert.That(scheduled, Is.False);
|
||||
Assert.That(bot.PendingPartyInvite, Is.Null);
|
||||
Assert.That(bot.LastPartyRequester, Is.Null);
|
||||
Assert.That(scheduled, Is.True);
|
||||
Assert.That(bot.PendingPartyInvite, Is.Not.Null);
|
||||
Assert.That(bot.LastPartyRequester, Is.SameAs(requester));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user