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); } } }