Commit Graph

28 Commits

Author SHA1 Message Date
Claude
7a7e63b40e Declare the parameter type enum where both packet structs see it
An enum which is declared inside a packet is generated as a nested type
of its struct. The ref struct of the same packet is generated into
another file and refers to the enum by its plain name, so it didn't
compile.

Declare it next to the other shared enums instead, which puts it into
the namespace - that's also where CharacterStatus lives, which is used
by a structure of another packet in the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSpK6jkyF8ZS5nYXyGwYxA
(cherry picked from commit 33703647e97d9fb13680086d0366f8200fe2865a)
2026-07-27 01:14:05 +03:00
Claude
066894c4f6 Send the available chat commands to clients which can show them
Adds the server side of the chat command list: a view plugin which
sends one ChatCommandInfo per available command, and the handler for
the request of a client which supports it.

Both are limited to the extended protocol, so classic clients don't
receive messages they can't understand. The commands come from
GetAvailableChatCommandInfos, so a player only learns about the ones
he may execute, and deactivated ones stay out.

Executing a command needs nothing new: the client sends the composed
command line as an ordinary chat message, which the chat message
processor already routes to the command without broadcasting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSpK6jkyF8ZS5nYXyGwYxA
(cherry picked from commit f3a9cb132c295863942bb0f06f40cde231e57907)
2026-07-27 01:14:05 +03:00
nolt
7325e1a1d6 Let strangers pick up party money and split it equally
Money has no owner - unlike a dropped item it can be picked up by anyone
standing on it. Deriving the drop's shares from the killer's party made a
stranger's pick up pay nothing and leave the money on the ground, destroying
zen that was previously taken and split by the picking party.

The recorded shares now apply only when the party picking the money up is the
one which earned it; otherwise there is no experience of theirs to follow and
the money is split equally between the picking party, as it was before and as
share-less money (e.g. from an item box) already is.

Covered by DroppedMoneyTest.StrangerPartySplitsPickedUpMoneyEquallyAsync and
EarningPartyReceivesMoneyByExperienceShareAsync.

(cherry picked from commit ae7cad81da6893cfbd1c28bb29811b7f0a10d540)
2026-07-27 00:54:17 +03:00
nolt
d7c589fc45 Keep a money basis for party members at max level without master quest
AwardExperienceAsync returned 0 for a character at the maximum level which has
not completed the master quest: it gains neither normal nor master experience.
Since the money of a kill is now derived from those returned values, such a
member got no zen at all in a party, while the same character still earns zen
when killing solo - AddExpAfterKillAsync returns the calculated amount there,
regardless of whether it could be applied.

The normal experience is now always calculated and returned, and only awarded
when the character can still gain it.

(cherry picked from commit 7cdb34b7c58f5924427b97ea17f26c3fe0f37719)
2026-07-27 00:54:16 +03:00
nolt
6307ecfc24 Derive each player's zen from the experience they actually gained
The money amount of a monster drop is computed as "gained experience + 7", but
in a party it was neither derived from, nor distributed like, that experience.

DistributeExperienceAfterKillAsync returned a party total without the game rate
and the personal experience rates, while AddExpAfterKillAsync returned a value
which had them applied. Both landed in the same variable in OnDeathAsync, so the
party number was orders of magnitude smaller. That mismatch was worked around by
recalculating the killer's solo experience for money purposes, which pinned the
party pool to a solo-sized amount: with the pool then split by member count, a
party of three received about a third of the solo zen each, while its experience
pool grows with the party size.

AwardExperienceAsync already computes the per member experience with all rates
applied, it just discarded it. It now returns that value, the distribution
returns the per member breakdown, and the money is split proportionally to it.
The workaround is gone, the pool follows the experience, and each member's zen
matches their own level, their own rates and their own master/normal branch. The
units lost to the integer division are handed to the shares which were cut the
most, one each, so no member is systematically favoured over many kills.

Two defects around the money rate are fixed on the way:

- MoneyAmountRate was applied for the killer when the drop was created and again
  for the receiver when a party picked it up, so a rate of 3.0 paid a party 9x.
  On the "money straight into the inventory" path for a solo killer it was not
  applied at all. It is now applied exactly once, for the receiver.
- ClampMoneyOnPickup was only honoured for solo pick ups. A party member at
  MaximumInventoryMoney silently lost their share, because the drop was consumed
  as soon as any other member could take one. The clamp now runs on the shared
  payout path, after the money rate, so it clamps the amount actually credited.

Money without a per player breakdown, such as the fixed amount of an item box,
keeps being split equally. Shares of players who are no longer eligible are
redistributed among the remaining ones instead of being lost.

(cherry picked from commit 53f25aca08e690255571f2bb6c796bb7c10934a2)
2026-07-27 00:54:16 +03:00
sven-n
e910845383 Merge pull request #841 from nolt/bot-behaviour
Make the server-side bots hold up as a population

(cherry picked from commit 88535b63a958fee9803d5fc3a4bdac5a3318d221)
2026-07-23 11:38:09 +03:00
sven-n
0e2c94b84b Merge pull request #836 from nolt/speedhack-exempt-offline-players
Don't check offline players for speedhacking

(cherry picked from commit 6380b269770f0c949b03794ca701876c13636513)
2026-07-23 11:38:09 +03:00
sven-n
215f48bfc9 Merge pull request #837 from nolt/fix-money-drop-lock
Release a money drop again when nobody could take it

(cherry picked from commit 83e942e4c778a14eeade8433632a6180ef286e3f)
2026-07-23 11:38:09 +03:00
sven-n
d04cbecae3 Merge pull request #820 from nolt/feature-bots
(cherry picked from commit b10de0645a869485fbb5771a739abd06b5708c2d)
2026-07-23 11:38:08 +03:00
sven-n
d22ec20a04 Merge pull request #825 from eduardosmaniotto/feature/configurable-npc-buffs
feature: configurable npc buffs
(cherry picked from commit 6a061fcad756e644dadf3721e84e3cce2a25bceb)
2026-07-23 11:37:22 +03:00
sven-n
ed32ebdfd3 Merge pull request #827 from Rhefew/feature/ip-connection-limit
feat(plugins): implement customizable maximum concurrent connections

(cherry picked from commit 0e30df9ae22a265c3486dea1eba259567631eb87)
2026-07-23 11:35:43 +03:00
Acentech Dev
ceacc11446 feat(hs): apply mapped class buffs to team on statue break 2026-07-21 00:15:52 +03:00
Acentech Dev
712773c332 feat(hs): sequential statue spawn + guard mobs + win detection
Adds HeykelSavasiContext runtime logic for Task 4.2: per-team statue
break progress/winner tracking (extracted into a pure, testable
StatueProgressState), OnDestructibleDied wiring against the base
MiniGameContext's existing Destructible->Died auto-subscription, and
SpawnStatueAsync which spawns the next statue (561) + 4 guards (580)
via the map initializer using placeholder line-of-statues coordinates.
OnGameStartAsync now spawns each team's first statue after the warp.
2026-07-21 00:07:00 +03:00
Acentech Dev
968eb0c155 feat(hs): HeykelSavasiContext team tracking + balance rule
Adds the HeykelSavasiTeam enum (None/Red/Blue) and the HeykelSavasiContext
skeleton (subclass of MiniGameContext) with team registration
(AssignTeam/GetTeam/PlayersOf/TeamCount) and a join-balance rule
(|Red-Blue| <= 2 after the prospective join), exposed as a pure static
IsJoinAllowed for fast unit testing without constructing the full context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 22:59:15 +03:00
Acentech Dev
1de06d83ab fix(reset): warp the player home instead of moving him silently
Some checks failed
.NET Core / build (push) Has been cancelled
MoveHomeAsync wrote PositionX/PositionY/CurrentMap/Rotation on the character
record directly, which is a partial copy of Player.PlaceAtGateAsync: it placed
the player but skipped removing him from the map and telling the client.

Player.Position is backed by those very fields, so the coordinates jumped on the
server while the client never got a map change. The client then interpolated a
walk to the new spot and the character visibly slid across the map after a reset.

WarpToAsync does the same placement plus the map removal and the map change
notification, and it handles respawning on the same map. It is the path every
other caller uses (duel room, gate NPCs, mini games, castle siege portal).

The existing tests all ran with MoveHome = false, which is why this path was
never covered. The new test pins the notification: it fails on the old code
because MapChangeAsync is never invoked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:26:04 +03:00
Acentech Dev
f135e90899 fix(CS): stop crown re-capture loop + show seal panel only to the master
Some checks failed
.NET Core / build (push) Has been cancelled
- The occupier can no longer re-register its own throne (canCapture = eligible != occupier),
  which caused a repeating 60s panel loop; only a DIFFERENT guild can contest.
- The 60s registration panel (0xB2/0x15) is now sent ONLY to the master on the crown,
  not broadcast to switch-holders/other players. Shield(0x16) + capture(0x18) still broadcast.
+1 test (15 total).
2026-07-15 16:22:13 +03:00
Acentech Dev
3dd4881406 feat(CS): authentic Crown-hold throne capture (S6 protocol) replacing Sinior-talk
Some checks failed
.NET Core / build (push) Has been cancelled
Break all gates + hold both switches (defenses down) -> the Crown shield drops
(C1 B2 16=0). The guild master then stands on the Crown (176,212) and holds for
CrownHoldDuration (default 60s, client shows a 60s countdown via C1 B2 15) to
capture; capture broadcasts C1 B2 18 + golden text and sets the occupier. Losing a
switch or leaving the crown resets the hold (contestable until the siege timer ends).
Sinior (223) is now informational guidance. +2 tests (14 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:04:10 +03:00
Acentech Dev
4651ef232c refactor(CS): schedule + periods are config-owned, AdminPanel-editable
Some checks failed
.NET Core / build (push) Has been cancelled
Move the auto-schedule from context-owned state to the plugin config (single source
of truth): RegistrationOpenDays + RegistrationOpenTimes + Registration/Preparation/
SiegeDuration are all editable in the AdminPanel plugin config and take effect live
(context refreshes its config reference each tick via UpdateConfiguration). /csschedule
now writes the config. Removed the duplicate Persisted schedule fields. 12 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:52:07 +03:00
Acentech Dev
7cf790c2a8 feat(CS): on-map siege countdown timer (S6 client protocol C1 B2 17/1E)
Some checks failed
.NET Core / build (push) Has been cancelled
Client already renders the siege-map countdown but never received the packets.
New ICastleSiegeStatusViewPlugIn + RemoteView impl send the raw S6 packets:
C1 B2 17 (battle start/stop flag, arms the countdown) and C1 B2 1E (remaining
hour/minute). CastleSiegeEventPlugIn broadcasts them to players on the battle map
every 10s during Siege and sends stop at Settlement. No client changes. +1 test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:45:01 +03:00
Acentech Dev
ac6700a356 feat(CS): weekly auto-schedule (day-of-week + UTC time), persisted, /csschedule GM cmd
Some checks failed
.NET Core / build (push) Has been cancelled
The siege can now auto-open registration on scheduled days/time instead of only
manual /csphase. Schedule is context-owned state (like owner), persisted across
restarts via the same config-JSON path. New GM command /csschedule sets/views/clears
it (e.g. /csschedule Sunday 20:00, UTC). +2 unit tests (11 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:47:29 +03:00
Acentech Dev
9bdd57703f feat(CS): persist owner/phase/registrations across restarts via plugin config JSON
Some checks failed
.NET Core / build (push) Has been cancelled
Castle Siege state was in-memory, so the castle owner (and thus the P4 hunting-map
reward) reset on every server restart/redeploy. Now the context marks itself dirty
on any persistable change (phase transition, registration, owner set); the plugin's
periodic tick writes a snapshot into its own PlugInConfiguration CustomConfiguration
(a JSON blob already stored in PostgreSQL - no schema migration) and restores it on
startup. Battle state (defenses/switches/occupier) stays transient. +2 unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:17:34 +03:00
Acentech Dev
72ebc96e28 feat(CS-P3): throne registration via Sinior/Crown talk (223/216) after defenses down + both switches held; reachable defense positions
Some checks failed
.NET Core / build (push) Has been cancelled
2026-07-15 02:49:27 +03:00
Acentech Dev
db8bdd394f feat(CS-P3): PROPER siege - position-based Crown Switch hold (stand on them) + gates & statues destructibles + auto throne capture; remove non-working talk handlers
Some checks failed
.NET Core / build (push) Has been cancelled
2026-07-15 02:28:14 +03:00
Acentech Dev
c50af5e5c2 feat(CS-P3): full siege objective chain - guardian statues (Destructible spawn) + dual Crown Switch hold + throne capture gating 2026-07-15 01:52:19 +03:00
Acentech Dev
9f2078d724 feat(CS-P3): throne capture + settlement winner in state machine + tests
Some checks failed
.NET Core / build (push) Has been cancelled
2026-07-15 00:48:09 +03:00
Acentech Dev
73de807bef feat(CS-P1): Castle Siege phase state machine (in-memory, time-injected) + tests 2026-07-14 22:40:24 +03:00
Acentech Dev
2ced76b303 test(ADAMU-CUSTOM): regression test for GameMap.GetNpcByNumber (remote-NPC) 2026-07-14 21:36:34 +03:00
Acentech Dev
36fc125d5c baseline: OpenMU upstream b5a0961 (fresh source) 2026-07-14 19:00:35 +03:00