배경
지금 팔 수 있는 것은 자원뿐이다. 인벤토리 캐릭터·장비 탭에서 우클릭하면
"판매는 자원 탭만 지원합니다"로 끝난다(StorageGridPresenter·SellCartPresenter).
막고 있는 것은 클라가 아니라 판매 패킷의 축이다.
// 지금 — 아이템 TID 축. 종류 + 개수만 나른다
C_ItemSellRequest { List<ItemInfo> Items } // ItemInfo = { ItemTID, Count }
캐릭터·장비는 개체다(CharacterId · EquipId — long PK). 같은 TID의 개체가 여러 개이고
레벨·경험치·장착 상태가 개체마다 다르므로 "TID를 N개 판다"로는 어느 개체인지 지정할 수 없다.
가격 쪽 상태도 갈린다.
| 축 | 가격 컬럼 | 상태 |
|---|---|---|
| 아이템 | ItemTable.BasePrice |
✅ 162행 전부 채워져 있다 |
| 장비 | EquipTable.BasePrice |
✅ 69행 전부 채워져 있다(70~360 등) — 컬럼은 이미 있다 |
| 캐릭터 | CharacterTable.BasePrice |
✅ 2026-09-30 추가 — 등급별 고정값 Common 50 · Uncommon 100 · Rare 250 · Epic 600 · Legendary 1500 · Mythic 4000 (뽑기 1회 1000 G의 기대 판매가 약 216 G) |
즉시 판매가 = BasePrice의 100%는 거래 3.1에서 확정됐다.
장비는 이 규칙을 그대로 쓰면 되고, 캐릭터만 새로 정해야 한다.
할 일
기획
- [x] 캐릭터를 팔 수 있게 할지 정한다. → 판다(2026-10-01 · #47). 경매장에도 올린다 캐릭터는 character 1.1에서 "중복은 개체를 늘린다 · 조각·강화로 바꾸지 않는다"로 확정돼 중복 개체의 출구가 하나도 없다. 판매를 열거나, 다른 출구(T-076)를 열거나 둘 중 하나는 필요하다
- [x] 캐릭터 판매가 — 등급별 고정값으로
CharacterTable.BasePrice에 넣었다(2026-09-30 태웅). 레벨 반영은 하지 않는다(필요하면 다시 연다) - [x] 레벨이 오른 캐릭터·장착 중인 장비를 팔 수 있는지 → 레벨은 막지 않는다. 착용 중인 장비 · 배치됐거나 장비를 낀 캐릭터 · 마지막 남은 캐릭터는 막는다 (2026-10-01)
- [x] 장비 판매가는
EquipTable.BasePrice×SellRatePermille(100%) — 아이템과 같은 비율
서버
-
[x] 개체 축 판매 패킷을 판다. (2026-10-01) 요청 이슈: #47 ③ 기존
C_ItemSellRequest를 고치지 않고 따로 둔다 — 아이템은 TID·수량 축이 맞고, 개체는 PK 목록 축이 맞다```csharp C_EntitySellRequest { List<long> CharacterIds, List<long> EquipIds } S_EntitySellResponse { EResultCode Result, long GainedGold } ``` -
[x] 거절 코드 —
InvalidSellRequest(빈 요청·중복) ·EquipNotOwned·CharacterNotOwned·SellEquipWorn(304) ·SellCharacterBusy(305) ·SellLastCharacter(306) -
[x] 전부 성공하거나 전부 실패한다 —
S_ItemSellResponse와 같은 원칙 -
[x] 판매 후
S_CurrencyResponse로 잔액을 내린다. 개체 삭제는 Ok면 클라가 요청한 개체를 지운다(삭제용 싱크 패킷이 따로 없다) -
[x] 테스트(
UserEntitySellTest·SellEntitiesRepositoryTest) · 프로토콜 미러
클라
- [x] 담기를 캐릭터·장비 탭으로 넓혔다 — 칸 우클릭으로 하나씩 (2026-10-03). 일괄 담기는 자원만
- [x] 요청이 두 패킷으로 나뉜다 — 섞이면 둘 다 보내고 응답이 다 와야 성공·실패를 말한다(
SellCartPresenter) - [x] 장착 중·배치 중·장비 낀 캐릭터·마지막 캐릭터는 담기 단계에서 막고 사유를 알린다(
EntityBlockText) · 담아 둔 뒤 그렇게 되면 카트가 뺀다 - [x]
ResultMessages에 새 거절 코드 문구 · Ok면 판 개체를 지운다(PlayerDataModel) - [x] 실측 — 장비·캐릭터를 담아 즉시 판매 → 골드 증가 · 인벤토리에서 사라짐 · 막힌 개체는 담기지 않음 (2026-10-03 사용자 실측)
완료 조건
인벤토리 장비 탭에서 장비를 우클릭해 담고 판매하면 EquipTable.BasePrice만큼 골드가 들어오고
인벤토리에서 사라진다. 장착 중인 장비는 담기지 않는다. 캐릭터도 기획이 판매를 열기로 했다면 같게 동작한다.
막고 있는 것 / 선행 일감
- 기획·서버 완료(2026-10-01) · 클라 배선 완료(2026-10-03) — 남은 것은 실측이다 (담당을 클라로 넘김)
- T-076와 같은 결정을 공유한다 — 중복 개체의 출구를 판매로 둘지 재료로 둘지
- 인벤토리 장비 탭 T-043 · 장착 UI T-074
관련 일감
T-086 — 배치·장착 개체는 인벤토리 목록에 없다2026-09-25 철회. 배치·장착 중인 개체도 인벤토리에 그대로 보인다(딤 + '배' 마크). 그래서 아래 "담기 단계에서 막는다"는 여전히 필요하다 — 눌리는 칸이 화면에 있다.StorageSlotSource.IsAway가 판정을 갖고 있으니 담기 쪽에서도 그것을 쓴다. ⚠️ 그래도 서버 거절 코드는 그대로 둔다 — 클라를 못 믿는 자리다.
관련 커밋
33f5a5a캐릭터 경매 · 캐릭터·장비 즉시 판매 (2026-10-03)- 이 커밋 — 실측 완료 · 보관
참고
거래 README3.1 — 즉시 판매가 =BasePrice100%MikaPacket.cs—C_ItemSellRequestSellCartPresenter