Migration guide
This page lists the steps a host has to take when an adapter release breaks compatibility. Releases without an entry here are drop-in: bump the version and restart.
How breaking changes are announced
Section titled “How breaking changes are announced”- Every module is versioned on its own (
core/v…,api/v…,db-sync/v…), and every release appears in the changelog with its notes. - A release that needs action from the host gets a section on this page, newest first: which module and version, what changed, and what to do.
- The documentation always describes the current version.
nx-gs-adapter-api 0.125.0, nx-gs-adapter-core 0.54.0: APP_BAIT_LEVEL_MISMATCH, APP_NO_ROD
Section titled “nx-gs-adapter-api 0.125.0, nx-gs-adapter-core 0.54.0: APP_BAIT_LEVEL_MISMATCH, APP_NO_ROD”Additive: a host that sends neither reason needs no change. Take both modules together: the core is what accepts the
new reasons from NxAppSessions.onActionEnded.
- New end reason
LogoutReason.APP_BAIT_LEVEL_MISMATCH: the chosen bait left the hand because the character’s level left its range. The bait stack is still in the inventory; a stack that is gone staysAPP_OUT_OF_BAIT. - New end reason
LogoutReason.APP_NO_ROD: the rod left the hands, its conditions failed or it is gone. APP_INTERRUPTEDno longer covers lost gear. It means the game stopped the placed character’s action: it was moved off its spot, the game closed the store, or the action could not resume. A store from the app that the game closed ends with it. A host on an older core still sendsAPP_INTERRUPTEDfor lost gear until it is restarted on the new core.- Core 0.54.0 accepts
APP_TIME_UPfrom the host for a session without a plan: an app store whose paid offline time ran out. A session with a plan ends by the engine’s own timer, and the core refusesAPP_TIME_UPfor it. Older cores ignoreAPP_TIME_UPfrom the host. - Send a new reason only once the platform accepts it: a new reason reaches the adapter first, then the platform, then the host.
nx-gs-adapter-api 0.124.0, nx-gs-adapter-core 0.53.0, nx-gs-runtime-sync-core 0.6.0, nx-gs-db-migrations 0.6.0: session results, item snapshots, runtime start gate
Section titled “nx-gs-adapter-api 0.124.0, nx-gs-adapter-core 0.53.0, nx-gs-runtime-sync-core 0.6.0, nx-gs-db-migrations 0.6.0: session results, item snapshots, runtime start gate”Breaking. Take the four modules together. Switch the host in the release that takes it.
You must call NxAdapter.runtimeSync().hostStarted(), or runtime sync sends nothing. Call it once, right after you
publish ServerStartedEvent, whether that event went out or is held until the events channel binds. There is no timer
and no fallback: a host that never calls it gets no ticks. Call NxAppSessions.hostReady() before it.
CharacterPresenceEvent.charIdis nowcharacterId, in the builder and on the wire.- Every
charIdand*CharIdof the api is nowcharacterIdand*CharacterId(buyerCharIdtobuyerCharacterId,targetCharIdtotargetCharacterId): commands, results, events andRatingDbDto. Builder and wire both change. charNameandtargetCharNameare nowcharacterNameandtargetCharacterName, inChatMessageEventandTelegramCharacterLinkCommand. Builder and wire both change.TransferCharToAccountCommandis nowTransferCharacterToAccountCommandandTelegramCharLinkCommandis nowTelegramCharacterLinkCommand, with their*Resultclasses. Commands route by simple class name, soNx-Message-Typechanges too: register the handler under the new class, and a sender of the old name getsUNSUPPORTED_COMMAND.CharacterPresenceEventtakesresult(SessionResult), ononline = falseonly. A client session sets it through the builder; an app session gets it from the engine.activityis allowed withsource = CLIENTforOFFLINE_PRIVATE_STOREonly, the native offline store session.OfflineSessionHost.takeSessionResult(session)is a newdefaultmethod answeringnull. Override it to report results. The engine calls it once after everyremoveFromWorld, including a rolled-back start and a character that was never placed, so a recorder you keep by session id can be released there, except when the end races a graceful stop that already kept the block: that end skips the call. It also runs at a graceful stop for each session left open.OfflineSession.getCarriedResult()holds the running block after a graceful stop, on the viewsplaceInWorldandstartActionget during a restore. Seed your recorder with it. An empty block means nothing was recorded;nullmeans the block was lost, and the end will saypartial.- A result over
SessionResult.MAX_JSON_BYTESis dropped and the end goes out without it: folduniquesintoitemsbefore that. - New values:
LogoutReason.SOLD_OUTandAPP_OVERWEIGHT(onActionEndedaccepts the latter), and the refusalsNO_FISHING_EXPERTISE,BAIT_LEVEL_MISMATCH,INVENTORY_FULL,OVERWEIGHT.INVENTORY_FULLandOVERWEIGHTcarry their numbers, inCommandProblem.extensionsand inAppSessionCheckResult, which requires them through its builder.APP_INVENTORY_FULLno longer covers weight. - Item forms carry
ItemSnapshotinitemin place of their loose fields, and item commands usecharacterId.BuyLine.attributesForMatch()is what the buy check compares: an empty map counts asnull. ConnectContextgainsruntimeSync(): build it with the builder.- New key
app.shutdown.result-millis-per-sessioninl2nx-app.properties, and a migration that adds a column to the adapter’s session table, applied at start.
nx-gs-adapter-api 0.123.0, nx-gs-adapter-core 0.52.0: chat vocabularies as enums, chat settings, whisper delivery
Section titled “nx-gs-adapter-api 0.123.0, nx-gs-adapter-core 0.52.0: chat vocabularies as enums, chat settings, whisper delivery”Breaking (source) for Java code; the wire strings do not change. The chat constant classes become enums with the
same names on the wire, and three constructors take a new parameter. Switch the host in the release that takes this
api: no message changes shape, so a platform reading the old strings reads the new host as before. With the core
update chat with the app is off by default: a host that publishes game chat and handles SendChatMessageCommand sets
app.chat.enabled=true in its l2nx-app.properties to keep its current behaviour.
Renamed types (no deprecated copy):
| Before | After | Notes |
|---|---|---|
kafka.events.chat.WellKnownChatChannels |
kafka.events.chat.model.ChatChannel |
same constant names; ChatChannel.unknownToken(rawType) builds UNKNOWN_<int>, ChatChannel.fromToken reads a token (null when unknown) |
kafka.events.chat.ChatMetadataKeys |
kafka.events.chat.model.ChatMetadataKey |
the map key is key() (source, shadowed, rawType, serverWide, undelivered), not name() |
kafka.commands.chat.ChatAudiences |
kafka.commands.chat.model.ChatAudience |
same constant names |
ChatMessageEvent.channel, SendChatMessageCommand.channel and .audience stay strings; their builders also take
the enums. Code that put a constant string into metadata now writes ChatMetadataKey.X.key().
- Refusal reasons. New
SendChatMessageRefusalReason, written underSendChatMessageRefusal.REASON("reason"):CHAT_FORBIDDEN(FORBIDDEN),NOT_IN_CLAN,NOT_IN_ALLIANCE,NOT_IN_PARTY(INVALID_STATE),TARGET_NOT_FOUND(NOT_FOUND),ITEM_NOT_OWNED(VALIDATION_FAILED), and the newCHAT_DISABLED,CHANNEL_DISABLED,TARGET_OFFLINE(INVALID_STATE). The strings are the ones hosts already send: replace your own reason constants with the enum’sname(). - Chat settings. New
AppRulesmemberschatEnabled(),chatOnlineSendChannels()andchatOfflineSendChannels(), and the keysapp.chat.enabled(defaultfalse),app.chat.online-send-channels(defaultCLAN,ALLIANCE,PARTY,WHISPER) andapp.chat.offline-send-channels(defaultCLAN,ALLIANCE,WHISPER), see configuration. With chat off the host publishes no game chat and refuses every send from a character withCHAT_DISABLED; a command withoutsenderCharacterId(an announcement, a line under a staff name) is not chat with the app and still goes out. A send to a channel outside the list for the character’s current state is refused withCHANNEL_DISABLED; the host picks the list by whether the character is in game when the command runs. AppSettingsgains achatfield (ChatSettings), a new last-but-one constructor parameter: code that buildsAppSettingsitself passesnullor aChatSettings.SendChatMessageCommandgainsofflineDelivery(Boolean), the last constructor parameter (afterinitiator).false: aWHISPERto an addressee who is out of game is refused withTARGET_OFFLINEand nothing is published;null(an older platform) reads astrue.SendChatMessageResultgainstargetInGame(Boolean), the last constructor parameter. Set it on everyWHISPER:truewhen the packet went to the addressee’s game client,falsewhen the addressee was out of game and the message went to the app only; leave itnullon other channels. The platform reads delivery from it, not fromrecipients.- New
ChatMetadataKey.UNDELIVERED: the host sets it to"true"on theChatMessageEventof a whisper that reached no game client of its addressee (an in-game whisper toName*, or a send from the app to an addressee out of game).
nx-gs-adapter-api 0.122.0: private buy store slots in the runtime sync
Section titled “nx-gs-adapter-api 0.122.0: private buy store slots in the runtime sync”Breaking (source). The canonical constructor of CharacterRuntimeDto takes a new last parameter,
privateStoreBuySlots (Integer): the maximum number of lines a private buy store of the character may hold right
now, independent of privateStoreSellSlots. A provider computes it from the same rules as the sell limit and passes
null when it does not report it. Code that builds CharacterRuntimeDto itself (providers, tests) adds the argument.
nx-gs-adapter-api 0.120.0, nx-gs-adapter-core 0.51.0, nx-gs-gd-sync-core 0.16.0, nx-gs-db-migrations 0.5.0: command initiator, session checks, fishing gear
Section titled “nx-gs-adapter-api 0.120.0, nx-gs-adapter-core 0.51.0, nx-gs-gd-sync-core 0.16.0, nx-gs-db-migrations 0.5.0: command initiator, session checks, fishing gear”Breaking. Update the api, the core and gd-sync together and switch the host in one release.
- Command initiator. Every command carries a required
initiator(CommandInitiator): who triggered it (sourceisuseror the sending platform service;userIdis set exactly for a user; optionallabel,email,ip).NxCommandhasgetInitiator(), and every command constructor and builder takes it. The core refuses a command without a valid initiator withVALIDATION_FAILEDbefore your handler runs, and anOwnerVerifiedcommand also needs a user source, so a handler reads a valid initiator and never re-checks it. Itsiphas already passedClientIdentifiers. Code that builds commands itself (tests, tools) passes an initiator. - Removed
masterAccountIdandinitiatorIpfromStartOfflineFishingCommand,StopOfflineFishingCommand,ReviveCharacterCommandand the private store start commands (StartPrivateStoreCommand), with no deprecated copy. Readinitiator.getUserId()for the master account andinitiator.getIp()for the request IP, and pass them on toAppSessionStartRequestas before.BanCommand.issuedByandSendCaptchaCommand.issuedBystay: they are the name the record shows in the game, not the attribution. - Package move.
AppSessionStartRequest,AppSessionStartOutcome,ClientLoginOutcome,OfflineSessionandOfflineSessionPlacementmoved fromapp.l2nx.gs.adapter.api.spi.modeltoapp.l2nx.gs.adapter.api.spi.model.session. Replace the imports; nothing else changed in them. - Session checks.
NxAppSessions.check(characterId, spotPool, spotId)answers anAppSessionCheckOutcome(ALLOWED,CHARACTER_BUSYwith the open session,SPOT_TAKEN,UNAVAILABLE) without writing anything, taking a spot or callingOfflineSessionHost. The newCheckOfflineFishingCommand(resultAppSessionCheckResult) asks whether a fishing start with these inputs would start now: run your start steps up to the first side effect and callcheckwhere the start callsstart. A refusal is a successful check (OK,allowed = false). Until you register a handler the adapter answers the command as unsupported. Code that implementsNxAppSessionsitself addscheck. - New refusal vocabulary.
AlreadyActiveScope.IP(your per-IP limit, counted underinitiator.ip; an absent IP limits nothing), and theAppSessionRefusalReasonvaluesNO_FISHING_SHOT,INVALID_FISHING_SHOTandFISHING_SHOT_GRADE_MISMATCHfor the new optionalStartOfflineFishingCommand.fishingShotItemId. ADAILY_LIMITrefusal may now carryretryAt, the moment your daily allowance resets; leave it out when your build does not know that moment. ActivityMetadataKeynames the keys of an activity-usage rowmetadata(elapsed,remaining,rodItemId,baitItemId,fishingShotItemId,spotPool,spotId); writekey()instead of your own strings. The map stays open.- Open-sessions snapshot. The core publishes
AppSessionsSnapshotEvent(the ids of every open app session of the server) every 60 s and when the app-sessions module starts, and republishes a session end until the platform has acknowledged it. Bumpnx-gs-db-migrationsto 0.5.0 with the core: its changesets add the column this needs. - Fishing gear catalog. New game data providers
FishingBaitProvider,FishingRodProviderandFishingShotProvider(entitiesfishingbait,fishingrod,fishingshot). Optional: a server without fishing registers none. A host that implements them registers each inMETA-INF/servicesand returnsnullfromsnapshot()until its data is loaded. - An empty game data snapshot now deletes. A registered provider that returns an empty collection (or an empty
OptionalfromGearScoreRulesetProvider) makes the platform delete every row of that entity for the server. Returnnullwhile your data is not loaded, never an empty collection.
nx-gs-adapter-api 0.119.0: clanId and allianceId on chat messages
Section titled “nx-gs-adapter-api 0.119.0: clanId and allianceId on chat messages”Breaking for Java code only. ChatMetadataKeys.CLAN_ID and ChatMetadataKeys.ALLIANCE_ID are removed, with no
deprecated copy. ChatMessageEvent now has the fields clanId and allianceId (Long, nullable) on every channel;
its constructor takes them as two new trailing parameters, and the builder has clanId(...) and allianceId(...).
- A host that wrote the clan and alliance ids into the event
metadatasets the two fields instead and stops writing those keys: the platform no longer reads them frommetadata. - The fields hold the speaker’s clan and alliance at the moment of speaking. A message without a legal sender carries
the addressee: on
CLANits clan and alliance, onALLIANCEonly the alliance. No clan or alliance meansnull. - Code that calls the
ChatMessageEventconstructor directly passesnullfor both new parameters, or the ids.
nx-gs-adapter-api 0.119.0, nx-gs-adapter-core 0.50.0: revive of a dead character
Section titled “nx-gs-adapter-api 0.119.0, nx-gs-adapter-core 0.50.0: revive of a dead character”Additive: a host that does not handle the new command needs no change. Until it does, the platform offers no revive for
the server (the published app settings carry no revive group), and an older host answers the command as any unknown
one.
- New
ReviveCharacterCommand/ReviveCharacterResult: gets a dead character up at a restart point (TOWN), with the client connected or already closed. A host that wants the feature registers a handler for it and refuses with aReviveRefusalReasoncode where the revive is not possible. - New
AppRulesmembersreviveEnabled(),reviveTownMode()andreviveTownSpotPools(), and the keysapp.revive.enabled(defaulttrue),app.revive.town.mode(NATIVEby default, orSPOT_POOL) andapp.revive.town.spot-pool, see configuration. A server file without them keeps working.AppSettingsgains arevivefield; its public constructor takes it as a new parameter, so code that buildsAppSettingsitself passesnullor aReviveSettings.
nx-gs-adapter-api 0.117.1, nx-gs-adapter-core 0.49.1: LifeStoneGrade.JEWELRY, GameSettingsProvider
Section titled “nx-gs-adapter-api 0.117.1, nx-gs-adapter-core 0.49.1: LifeStoneGrade.JEWELRY, GameSettingsProvider”Breaking for Java code only. The LifeStoneGrade constant ACCESSORY is now JEWELRY (neck, ear and finger
slots); no deprecated copy is kept. A host that builds life stone data replaces the constant, nothing else; the grade
is sent by name, so a host not yet updated keeps sending ACCESSORY until it is rebuilt.
Additive: the optional GameSettingsProvider (see api) publishes the engine
configuration (enchant caps, attribute limits, mail fees, the offline private store fee). A host without it keeps
working and publishes no game settings; a host that implements it registers it in META-INF/services and returns
null from gameSettings(...) until its configuration is loaded. Two registered implementations fail the module.
nx-gs-adapter-api 0.116.0, nx-gs-gd-sync-core 0.15.0: StatModifier, augmentation catalog
Section titled “nx-gs-adapter-api 0.116.0, nx-gs-gd-sync-core 0.15.0: StatModifier, augmentation catalog”Breaking for Java code only. SkillStatModifier (package app.l2nx.gs.adapter.api.kafka.sync.gd.skill.effect)
is now StatModifier in app.l2nx.gs.adapter.api.kafka.sync.gd: skills and item options share it. Fields, getters,
builder and JSON are unchanged, and no deprecated copy is kept. A host that builds skill data replaces the import and
the class name, nothing else.
Additive: three new game data providers, ItemOptionProvider, AugmentationTargetGroupProvider and
LifeStoneProvider (entities itemoption, augmentationtargetgroup, lifestone). A host without them keeps working;
a host that implements them registers each in META-INF/services like the other providers and returns null from
snapshot() until its data is loaded. Update nx-gs-gd-sync-core together with the api, since it is the module that
publishes them.
nx-gs-adapter-api 0.115.0, nx-gs-adapter-core 0.48.0: open sessions of a type, APP_INTERRUPTED
Section titled “nx-gs-adapter-api 0.115.0, nx-gs-adapter-core 0.48.0: open sessions of a type, APP_INTERRUPTED”Additive: a host that uses neither addition needs no change.
- New
NxAppSessions.findOpen(ActivityType activity): every session of that type the adapter holds (running, awaiting the restore after a restart, a start in flight, an end still being written), ordered bystartedAt, then id. It reads memory only, so any thread may call it; without a running session engine it answers an empty list. Call it right beforehostReady()to count the sessions awaiting the restore against your per-account and per-device limits and to keep their spots (OfflineSession.getSpotPool(),getSpotId()) out of your free-spot pick. - New end reason
LogoutReason.APP_INTERRUPTED, accepted byNxAppSessions.onActionEnded: the game stopped the placed character’s action (it was teleported, the tool or consumable the action needs vanished, the action failed to restart). Use it instead ofKICKED(a staff kick) orAPP_STOPPED(the player’s stop). Send it only once the platform accepts it: a new reason reaches the adapter first, then the platform, then the host.
nx-gs-adapter-api 0.114.0, nx-gs-adapter-core 0.47.0: default title of a placed character
Section titled “nx-gs-adapter-api 0.114.0, nx-gs-adapter-core 0.47.0: default title of a placed character”app.phantom.title.text now defaults to l2nx.app instead of no title. A server file without the key shows
l2nx.app over characters the app placed in the world. To keep the character’s own title, set the key to an empty
value (app.phantom.title.text=). No code change for hosts.
nx-gs-adapter-api 0.112.0, nx-gs-adapter-core 0.46.0, nx-gs-db-migrations 0.4.0: private-store start cooldown
Section titled “nx-gs-adapter-api 0.112.0, nx-gs-adapter-core 0.46.0, nx-gs-db-migrations 0.4.0: private-store start cooldown”What changed. A private store started from the app can no longer be restarted right away to dump prices: a new start of a character waits a few minutes after the real start of that character’s previous app private store. The adapter carries the rule and the data; the host enforces it.
- New key
app.private-store.start-cooldown-minutesofl2nx-app.properties(default5,0turns the rule off,0-1440; see configuration) and its typed valueAppRules.privateStoreStartCooldownMinutes(). - New
NxAppSessions.findLastStartedByCharacter(int characterId, ActivityType activity): the character’s session of that type with the latest real start, open or closed. Same rules as the master-account read: starts that never really happened do not count. - Renamed:
NxAppSessions.findLastStarted(UUID, ActivityType)is nowfindLastStartedByMasterAccount(UUID, ActivityType); no deprecated copy is kept. - Both reads complete the future exceptionally on a database failure and also while the adapter’s session engine does
not run (still booting, disabled, or not started). Before this release the master-account read answered an empty
Optionalin that case. - New refusal token
PrivateStoreReason.COOLDOWN(INVALID_STATE, withretryAt). - The settings snapshot
AppSettings: newprivateStoreStartCooldownMinutes, andstartCooldownMinutesis renamed tofishingStartCooldownMinutes(getter and builder method too). The constructor takes the new field, so code that buildsAppSettings(tests included) uses the builder. - New changeset
0010ofnx-gs-db-migrations: an index of the adapter’s session table that serves the per-character read. The adapter applies it at start, like the earlier changesets.
What to do.
- In every private-store start from the app (sell, package sell, buy), when
NxAppRules.current().privateStoreStartCooldownMinutes()is above0, readfindLastStartedByCharacter(characterId, ActivityType.OFFLINE_PRIVATE_STORE)and refuse the start withCOOLDOWNandretryAt= itsstartedAtplus the cooldown while that moment is still ahead. Wait for the future on your command thread, never on the adapter IO pool. - A failed read refuses the start as temporarily unavailable, never as “no previous store”.
- Never apply the cooldown to a stop: closing a store is always allowed.
- Rename calls of
findLastStarted(masterAccountId, activity)tofindLastStartedByMasterAccount, and uses ofAppSettingsstartCooldownMinutestofishingStartCooldownMinutes. - Upgrade
nx-gs-db-migrator-liquibase5to 0.2.2: it bringsnx-gs-db-migrations0.4.0 (changeset 0010).
nx-gs-adapter-api 0.111.0, nx-gs-adapter-core 0.45.0: app settings file
Section titled “nx-gs-adapter-api 0.111.0, nx-gs-adapter-core 0.45.0: app settings file”What changed. The adapter now owns the per-server app settings: the file l2nx-app.properties (see
configuration), validated as a whole and reloaded without a restart.
The host no longer supplies or publishes settings. A host that implements OfflineSessionHost or calls
NxAppSessions must adapt in the same release that upgrades the api and the core:
OfflineSessionHost.settings()is removed; delete the implementation.OfflineSessionSettingsis removed. Its values are now keys of the file: return threshold, retention days, restore batch size and pause.NxAppSessions.publishSettingsis removed: the adapter publishes the settings itself. Stop calling it and drop the host code that builtAppSettings.OfflineSessionhas a new nullablereturnThresholdMs, fixed at the session’s start; thereturnToOriginflag ofremoveFromWorldis computed from it.- New:
NxAppSessions.findOpenByMasterAccountandfindLastStarted, andNxAppRules(NxAdapter.appRules(),ConnectContext.appRules(),CommandContext.appRules()) for the typed rules.
What to do.
- Remove
settings()from yourOfflineSessionHostand the calls topublishSettings. - Create
l2nx-app.propertiesnext tol2nx.properties, starting from the reference file, and move your former settings values into it. Every session type is off by default: enable the ones the server offers. - Read the rules (cooldowns, durations, windows, spot pool) from
NxAppRules.current()once per check instead of from your own configuration. - If you build
OfflineSessionyourself (tests included), pass the newreturnThresholdMs.
The schema changes (a nullable column and an index on the adapter’s session table) are applied by the adapter’s migrator at start.
nx-gs-adapter-api 0.110.0: private-store contract
Section titled “nx-gs-adapter-api 0.110.0: private-store contract”What changed. The private-store commands and events were reshaped. A host that handles StartPrivateStore*
commands, refuses a store start or purchase, or reads purchase events must adapt in the same release that upgrades
the api:
- A new
StartPrivateStoreBuyCommandopens a buy store; its lines areWantedLine(itemTemplateId,count,priceAdena).StartPrivateStoreSellCommandandStartPrivateStorePackageSellCommandnow extend the newStartPrivateStoreSellingCommand, which carriesgetLines()andgetSellerFeePercent(); the common baseStartPrivateStoreCommandno longer has them. DroppedLinetakes a requiredlineIndex(0-based index into the command’slines), and itsitemIdis nullable.PrivateStoreFees,MAX_FEE_PERCENTandBuyFromPrivateStoreCommand.MAX_LINESare removed: limits are the platform’s decision. A fee percent is validated only as not negative (seller fee also at most 100); refuse an oversized purchase withTOO_MANY_LINES.- The
WellKnown*private-store constant classes are replaced by the enumsPrivateStoreReason,DroppedLineReasonandPrivateStoreMetadataKey.SellerFeeis nowPrivateStoreFee, andPrivateStoreMetadatareads the seller and buyer fee from a purchase event’s metadata. - The purchase metadata
sourceis now aSessionSourcename,CLIENTorAPP(it wasin_gameorremote). - New lenient parsers return
nullfor an unknown token:SessionSource.fromTokenand thefromTokenof the new enums.
What to do.
- Replace the removed constants and classes with the enums and types above; take the
linesof a sell command fromStartPrivateStoreSellingCommand. - Fill
lineIndexin everyDroppedLineyou report. - Send
CLIENTorAPPas the purchasesource, and read it withSessionSource.fromToken. - Handle
StartPrivateStoreBuyCommandto support buy stores.
nx-gs-adapter-core 0.40.0: adapter-owned schema migrations
Section titled “nx-gs-adapter-core 0.40.0: adapter-owned schema migrations”What changed. The adapter now creates and updates its own l2nx_* tables in the game database itself, before
any module starts. A game server without a schema migrator on the classpath goes FAILED at start.
What to do on a game server (a login server is not affected):
- Run the server on Java 17 or newer.
- Add
nx-gs-db-migrator-liquibase5andorg.liquibase:liquibase-core5.x to the runtime classpath, in the same release that upgrades the core. - Make sure exactly one
JdbcConnectionSourceis registered, and that its database user has write and DDL rights on thel2nx_*tables andCREATEin the schema. - Remove the host’s own DDL for
l2nx_*tables, if it had any: the adapter owns them now. An existingl2nx_command_receiptstable is adopted as is, with its data. - If the host reads
l2nx_*tables during its boot beforeNxAdapter.start(), callNxAdapter.migrateSchema()first.
