L2Off / PTS

Connect your L2Off extender

Choose and implement the game-side path that consumes delivery jobs

The connector can queue a purchase, but only code inside the game server can apply it to a live character. Decide which situation matches the extender you actually deploy before leaving item delivery enabled.

SituationWhat you haveSupported delivery
Buildable extenderSource and a working toolchainItems and any other jobs you implement
Existing item-delivery extenderBuilt Vanganth, ilExt, or compatible forkItems only
No delivery featureBuilt extender with no queue consumerNone; the server cannot serve purchases

If you can build the extender

Create the shared queue and stored procedures:

l2off/portal_delivery/schema.sql on GitHub

The contract is:

  • L2Server polls after EnterWorld, applies each job to the live User, and acknowledges it.
  • CacheD runs lin_PortalDeliveryAsk and lin_PortalDeliveryDone over the existing socket.
  • Items use the extender's inventory call; skills use its acquisition and DB persistence calls.
  • Both DLLs must be deployed and restarted together.

Enable the poll in the extender configuration:

l2ext.ini
[ItemDelivery]
Enabled = 1
CheckInterval = 60

Preserve the positional SQL contract

The CacheD side binds columns by position and without indicator variables. The stored procedure must return every column in the documented order, using ISNULL for fields that do not apply to the current job. amount is INT because the extender ODBC helper binds 32-bit integers.

Require QUOTED_IDENTIFIER ON

The filtered unique index on command_id and every write through it require QUOTED_IDENTIFIER ON. Create both procedures with the setting enabled so SQL Server stores it with the module; CacheD's ODBC session otherwise runs with it off.

Verify the stored procedures
SELECT o.name, m.uses_quoted_identifier
FROM sys.sql_modules m
JOIN sys.objects o ON o.object_id = m.object_id
WHERE o.name LIKE 'lin_PortalDelivery%';

Both rows must read 1.

A wrong setting can duplicate paid items

If the acknowledgement update fails, the row remains pending and the extender can give the same item again on every poll. Do not enable sales until the query above and a replay test both pass.

Keep packet handlers inside the real table bounds

Handler tables are fixed-size arrays followed by per-opcode flags. Binding past the actual initialized size corrupts the next array instead of failing cleanly. Read the bound from the initializer, prefer an opcode still connected to the do-nothing stub, and use one opcode plus append-only subcommands for multiple Forgeport jobs.

If the extender already delivers items

Use its required table and positional columns, enable its ItemDelivery feature, and point [delivery.item].sql at that queue. Common built extenders use:

jobId, charId, itemId, itemAmount, enchant

Remove [delivery.skill], rename, gender, and unstuck sections unless that exact build consumes them. A working item-only portal is safer than advertised jobs that never run.

If there is no delivery path

Remove all item and skill delivery sections. The connector will not advertise them, and the server remains unavailable to players because purchased items cannot be delivered. The safe options are obtaining buildable extender source or deploying an extender with a real delivery feature.

Verify

  1. Compare the deployed DLL hashes with the build output.
  2. Restart L2Server and CacheD together.
  3. Queue one item and observe ask, apply, and acknowledgement.
  4. Replay the same command id and confirm no second item appears.
  5. Test a refused job and confirm the player's order closes with coins returned.

On this page