Skip to content

Finish crafted potion trading: AddPoison for poisons, temp IDs in trade UI - #7

Merged
absol89 merged 3 commits into
codex/player-trading-1.6.1170from
claude/crafted-potion-trade-r45fjg
Oct 6, 2026
Merged

absol89 merged 3 commits into
codex/player-trading-1.6.1170from
claude/crafted-potion-trade-r45fjg

Conversation

@absol89

@absol89 absol89 commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

Requested by absol89· project thread

Before: crafted potions were listed in the trade UI with their name and effects, but not their temporary ID. Traded poisons were recreated through AddPotion, which is the engine's potion-only path, so the result would likely fail the post-creation check and the trade would cancel. Effects were also re-sorted by form ID before recreation, so the recipient's potion could get a different effect order (and name) than the sender's.

After: each crafted potion row shows Temporary ID: FFxxxxxx plus its effects (Restore Health: 38 for 12s). On a successful trade the recipient's client recreates the potion with BGSCreatedObjectManager::AddPotion (AE ID 36167) or AddPoison (36168) using the sender's effects, magnitudes, durations and order. No ingredients are checked or consumed and no Alchemy skill applies. The sender's stack is removed as before.

How: AlchemyItem::Capture no longer sorts effects. Inventory::PotionData::operator== now compares effects order-insensitively, so recipe matching still works regardless of order. AlchemyItem::Create picks AddPoison when IsPoison is set. Both IDs come from CommonLibSSE/CommonLibVR-NG (RELOCATION_ID(35265, 36167) and (35266, 36168)). This PR targets the codex branch, so the diff contains only this step.

Verification: I built the encoding library and Code/tests on Linux with g++. All 12 test cases pass, including a new effect-order test. The client code isn't compiled here because it needs MSVC/Windows, and it still needs the in-game playtest in PLAYER_TRADING_POTIONS.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SSW5HGXzi9kAVm4zdsSaid


Generated by Claude Code

…emp IDs

Poisons are recreated through the engine's AddPoison call instead of
AddPotion, effects keep the sender's order (recipe comparison is now
order-insensitive), and the trade UI lists each crafted potion's
temporary ID alongside its effects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSW5HGXzi9kAVm4zdsSaid
@absol89 absol89 self-assigned this Oct 6, 2026
Each client hides partner items whose plugin forms or crafted effects it
cannot load, and its Ready request lists the partner items it accepts.
The server transfers only accepted items; the rest stay with their owner,
and senders see which of their offered items the partner cannot receive.
Crafted enchanted gear trades when its enchantment effects resolve.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSW5HGXzi9kAVm4zdsSaid
@absol89

absol89 commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner Author

Already on the branch: crafted potions travel as their recipe (effects, magnitudes, durations), not as the sender's temp ID. On a successful trade the recipient's client recreates the potion with BGSCreatedObjectManager::AddPotion. It never checks or consumes ingredients and doesn't use the recipient's Alchemy skill. The stack is then removed from the sender.

What I fixed:

Poisons: they were going through AddPotion too, which most likely makes poison trades cancel. They now use the engine's AddPoison call. I took its address ID (36168) from CommonLibSSE.
Effect order: effects were being re-sorted before recreation, so the recipient's potion could come out with a different effect order and name. The sender's original order is kept now, and recipe matching ignores order.
Trade UI: each crafted potion row now shows Temporary ID: FFxxxxxx plus its effects, for example Restore Health: 38 for 12s, under the potion's name.

What I checked: I built the shared encoding code and the C++ test suite on Linux, and all 12 test cases pass, including a new one for effect order. I couldn't compile the game client here because it needs Windows/MSVC. Nothing yet confirms the AddPotion/AddPoison calls work inside the game. The playtest steps are in PLAYER_TRADING_POTIONS.md on the branch.

And temporary ID poisons and potions are no longer hidden in the players item picker menu?

Yes. Player-crafted potions and poisons now show in the trade item picker under Potions and Poisons, and the All tab shows them too. Each one lists its temporary ID and its effects.

The picker still hides temporary items it can't rebuild on the other player's side, using the isUnsupportedTemporary check (TradeService.cpp:395, trade-ui.service.ts:11). That covers crafted enchanted gear, and potions whose effects have conditions or come from another temporary form. Vanilla and normally crafted potions don't hit that check. This is from reading the code: I haven't seen it in-game yet, and the CI builds on the PR are still running.

CI passed on PR #7, so the Windows client build and both Linux server builds compile with these changes. The only thing left before merging is the in-game playtest in PLAYER_TRADING_POTIONS.md, which will show whether the potion and poison creation calls work in the game.

I want crafted enchanted items to be able to trade if they have enchantments that can be recreated on the recipient's side. If an item is not able to be resolved, it will not show in the offer box for the other player. And both players will accept only the items they can see and receive.

A sends potion of magicmodriches and a crafted vanilla effect poison

B sends weapon of vanilla enchantment, and armor of doom mod the A player doesnt have

A sees and accepts ready the vanilla enchantment weapon, and B accepts ready vanilla effect poison

These 2 items trade, and the non-seen items stay with their owner and those odd plugins they require

Got it: each side only sees and receives partner items it can rebuild. I'm checking how enchanted items are captured now.

Hid partner items this game can't rebuild

Ready now lists accepted items; server moves only those

Marked sender items the partner can't receive

Tests pass (13 cases), pushed to PR #7

Waiting on CI builds

Your A/B scenario now trades the way you described. The change is pushed to PR #7. CI is building it now.

What each player sees: your game shows only the partner items it can rebuild. That means every plugin the item uses has to be loaded on your side. For crafted potions and crafted enchantments, every effect has to exist in your game too. Anything else is hidden from you.
What Ready means: when you ready, your game tells the server exactly which visible partner items you accept. The server moves only those items, and everything else stays with its owner. In your example, the weapon and the poison trade, while the modded potion and the doom armor stay where they are.
What the sender sees: after the partner readies, any offered item they can't receive gets the note "Partner can't receive this item. It stays with you."
Crafted enchanted gear can now be traded when the enchantment's effects exist on the recipient. It's recreated from the sender's effects.

A weapon coated with a crafted poison stays hidden from the partner for now, because the item doesn't carry the poison's recipe. I can add that if you want it. Yes poisoned weapons could trade if the poison effect could be recreated on recipient

The 13 shared and server test cases pass locally, including your exact A/B scenario. I added it as step 9 of the playtest in PLAYER_TRADING_POTIONS.md.

That is why it is important that custom names dont transfer, because we could only show in the partner offer list what would resolve to a matching item. If players have different plugin orders, we need to handle that gracefully

Both of those already hold, so I didn't change any code for this.

Custom names don't transfer. The trade packet doesn't include a name field, and the custom-name code is commented out (TESObjectREFR.cpp). Your game names each partner item using its own copy of that form. For a crafted potion, it uses the name of the potion your game would create. So whatever name you see in the partner list is the item you'll actually receive.
Different plugin orders already work. When players join, the server gives each plugin an ID based on its filename, and each client maps those IDs to its own load order by filename (ModSystem::HandleMods). Items carry that ID plus the form's ID inside the plugin, never a load-order slot. Here's how your earlier example works out:
An item from a plugin you have in a different slot still resolves to the right form.
An item from a plugin you don't have resolves to nothing, so it's hidden from you and stays with its owner.
Effects inside crafted potions and enchantments are resolved the same way.

I haven't tested mismatched load orders in-game yet. It's worth adding to the playtest: shuffle the plugin order on one client and check that the partner list and the received items still match.

Weapons coated with a crafted poison can now be traded. The change is pushed to PR #7 and CI is building it.

A poisoned weapon now carries its poison's effects and remaining uses, not the sender's temporary poison ID.
The recipient sees the weapon only if every poison effect exists in their game. When the trade completes, their game recreates the coating with AddPoison and keeps the same number of uses. If an effect is missing, the weapon is hidden and stays with the sender.
The trade list shows the coating under the weapon, for example Poisoned (3 uses left): followed by each effect.
Weapons coated with a vanilla or plugin poison work the same as before.

All 14 shared and server test cases pass, including a new one for poisoned weapons. I added step 10 to the playtest for this. In-game, check that the recipient's coated weapon actually applies the poison when it hits.

Applied crafted poisons now carry their recipe in the inventory entry and
compare by recipe instead of temporary ID. The recipient recreates the
coating with AddPoison when every effect resolves locally; otherwise the
weapon stays hidden and remains with its owner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSW5HGXzi9kAVm4zdsSaid
@absol89
absol89 marked this pull request as ready for review October 6, 2026 23:34
@absol89
absol89 merged commit 335b8a7 into codex/player-trading-1.6.1170 Oct 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants