namespace Aeshnidae.InstancesNoDat;
///
/// The line a player reads on stepping into a copy: what tier it is and what that
/// means, in numbers (Tom, 2026-09-19: "the arrival line is a go"). Sent once per
/// crossing, from the physics relocation that is the moment the player is really
/// inside. Other mods with something to add about the place - what its loot does -
/// hang a handler on ; Aeshnidae.AwakenedRewards binds to it
/// by reflection, so neither mod references the other.
///
public static class Arrival
{
/// (player, landblock, copy, tier) - fired after this mod's own line.
public static Action? OnArrival;
public static void Announce(Player player, ushort landblock, int copy)
{
try
{
if (player?.Session is null)
return;
var settings = Mod.Settings;
var name = Mod.NameFor(landblock);
var scope = Mod.ScopeOf(landblock, copy);
// A personal copy is a solo place, and a fellowship shares experience across
// the whole server: a character in one would take shares from fellows in
// other copies, or hand its tier-scaled kills out to them (Tom, 2026-09-19).
// So stepping into a personal copy is leaving the fellowship.
if (settings.LeaveFellowshipOnEntry && scope == InstanceScope.Personal && player.Fellowship is not null)
{
var fellowship = player.Fellowship.FellowshipName;
player.FellowshipQuit(false);
player.SendMessage($"You leave {fellowship}: {name} is yours alone, and a fellowship shares experience across the whole server.");
Mod.Trace($"{player.Name} left fellowship '{fellowship}' on entering copy {copy} of {landblock:X4}");
}
if (!settings.AnnounceArrival)
return;
var tier = Scaling.TierFor(landblock, copy);
if (Enclaves.IsEnclaveCopy(copy))
{
// The owning mod says what this place is (Towns: whose town); nothing from here.
}
else if (Variants.ChosenFor(landblock, copy) is { Unscaled: true })
{
// Same rule: the filling was supplied by another mod, so its difficulty is
// not this mod's tier and "tier 0, stock monsters" would be a wrong line
// (Aeshnidae.Challenge: a roster drawn from a score band). It says what the
// place is, on the hook below.
}
else if (Worlds.IsWorldCopy(copy))
{
// One line per arrival by teleport (a portal, a recall); walking between
// landblocks of the world's outdoors never comes here.
player.SendMessage(Worlds.Describe(Worlds.TierOfCopy(copy)));
}
else if (tier <= 0)
{
player.SendMessage(scope == InstanceScope.Shared
? $"{name}: one room for everyone on the server, stock difficulty."
: $"{name}, tier 0: your own copy, stock monsters, stock rewards.");
}
else
{
var harder = settings.DifficultyPercentPerEnlightenment * tier;
var richer = settings.RewardPercentPerEnlightenment * tier;
player.SendMessage($"{name}, tier {tier}: your own copy. Monsters +{harder:0.#}% (attributes on the tier-built ones; skills, health and damage on the rest), rewards +{richer:0.#}% experience and luminance.");
}
OnArrival?.Invoke(player, landblock, copy, tier);
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] arrival line failed: {ex.Message}", ModManager.LogLevel.Warn);
}
}
}