Skip to content

Commit 98a7903

Browse files
Add cash shop storage (#233)
* Add cash shop storage * refactor a little * move storage to client struct
1 parent ee36294 commit 98a7903

File tree

14 files changed

+897
-78
lines changed

14 files changed

+897
-78
lines changed

cashshop/handle_client.go

Lines changed: 151 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ func (server *Server) HandleClientPacket(conn mnet.Client, reader mpacket.Reader
2020
case opcode.RecvPing:
2121
case opcode.RecvClientMigrate:
2222
server.handlePlayerConnect(conn, reader)
23-
case opcode.RecvCashShopPurchase:
24-
server.playerCashShopPurchase(conn, reader)
23+
case opcode.RecvCashShopOperation:
24+
server.handleCashShopOperation(conn, reader)
2525
case opcode.RecvChannelUserPortal:
2626
server.leaveCashShopToChannel(conn, reader)
2727

@@ -76,11 +76,23 @@ func (server *Server) handlePlayerConnect(conn mnet.Client, reader mpacket.Reade
7676

7777
server.players.Add(&plr)
7878

79+
// Load cash shop storage
80+
storage, err := server.GetOrLoadStorage(conn)
81+
if err != nil {
82+
log.Println("Failed to load cash shop storage for account", accountID, ":", err)
83+
}
84+
7985
server.world.Send(internal.PacketChannelPlayerConnected(plr.ID, plr.Name, server.id, false, 0, 0))
8086

8187
plr.Send(packetCashShopSet(&plr))
88+
89+
// Send cash shop storage items to player (before wishlist and amounts, matching OpenMG order)
90+
if storage != nil {
91+
plr.Send(packetCashShopLoadLocker(storage, accountID, plr.ID))
92+
}
93+
94+
//plr.Send(packetCashShopWishList(nil, false))
8295
plr.Send(packetCashShopUpdateAmounts(plr.GetNX(), plr.GetMaplePoints()))
83-
plr.Send(packetCashShopWishList(nil, true))
8496
}
8597

8698
func (server *Server) leaveCashShopToChannel(conn mnet.Client, reader mpacket.Reader) {
@@ -143,7 +155,7 @@ func (server *Server) leaveCashShopToChannel(conn mnet.Client, reader mpacket.Re
143155
conn.Send(p)
144156
}
145157

146-
func (server *Server) playerCashShopPurchase(conn mnet.Client, reader mpacket.Reader) {
158+
func (server *Server) handleCashShopOperation(conn mnet.Client, reader mpacket.Reader) {
147159
plr, err := server.players.GetFromConn(conn)
148160
if err != nil {
149161
return
@@ -200,8 +212,26 @@ func (server *Server) playerCashShopPurchase(conn mnet.Client, reader mpacket.Re
200212
return
201213
}
202214

203-
if err, _ := plr.GiveItem(newItem); err != nil {
204-
plr.Send(packetCashShopUpdateAmounts(plrNX, plrMaplePoints))
215+
// Get cash shop storage
216+
storage, storageErr := server.GetOrLoadStorage(conn)
217+
if storageErr != nil {
218+
log.Println("Failed to get cash shop storage:", storageErr)
219+
plr.Send(packetCashShopError(opcode.SendCashShopBuyFailed, constant.CashShopErrorUnknown))
220+
return
221+
}
222+
223+
// Add item to storage instead of inventory
224+
slotIdx, added := storage.addItem(newItem, sn)
225+
if !added {
226+
log.Println("Failed to add item to cash shop storage")
227+
plr.Send(packetCashShopError(opcode.SendCashShopBuyFailed, constant.CashShopErrorExceededNumberOfCashItems))
228+
return
229+
}
230+
231+
// Save storage
232+
if saveErr := storage.save(); saveErr != nil {
233+
log.Println("Failed to save cash shop storage:", saveErr)
234+
plr.Send(packetCashShopError(opcode.SendCashShopBuyFailed, constant.CashShopErrorUnknown))
205235
return
206236
}
207237

@@ -219,6 +249,12 @@ func (server *Server) playerCashShopPurchase(conn mnet.Client, reader mpacket.Re
219249

220250
plr.Send(packetCashShopUpdateAmounts(plrNX, plrMaplePoints))
221251

252+
// Send buy success packet with the specific item that was just added
253+
addedItem, ok := storage.getItemBySlot(int16(slotIdx + 1))
254+
if ok {
255+
plr.Send(packetCashShopBuyDone(*addedItem, conn.GetAccountID(), plr.ID))
256+
}
257+
222258
case opcode.RecvCashShopBuyPackage, opcode.RecvCashShopGiftPackage:
223259
currencySel := reader.ReadByte()
224260
pkgSN := reader.ReadInt32()
@@ -356,6 +392,115 @@ func (server *Server) playerCashShopPurchase(conn mnet.Client, reader mpacket.Re
356392

357393
plr.Send(packetCashShopIncreaseInv(invType, plr.GetSlotSize(invType)))
358394
plr.Send(packetCashShopUpdateAmounts(plrNX, plrMaplePoints))
395+
396+
case opcode.RecvCashShopMoveLtoS:
397+
cashItemID := reader.ReadInt64()
398+
_ = reader.ReadByte()
399+
targetSlot := reader.ReadInt16()
400+
401+
storage, storageErr := server.GetOrLoadStorage(conn)
402+
if storageErr != nil {
403+
plr.Send(packetCashShopError(opcode.SendCashShopMoveLtoSFailed, constant.CashShopErrorUnknown))
404+
return
405+
}
406+
407+
var foundIdx = -1
408+
var foundItem *channel.Item
409+
for i := range storage.items {
410+
if storage.items[i].ID == 0 {
411+
continue
412+
}
413+
if storage.items[i].GetCashID() == cashItemID {
414+
foundIdx = i
415+
foundItem = &storage.items[i]
416+
break
417+
}
418+
}
419+
420+
if foundIdx == -1 || foundItem == nil {
421+
plr.Send(packetCashShopError(opcode.SendCashShopMoveLtoSFailed, constant.CashShopErrorUnknown))
422+
return
423+
}
424+
425+
removedItem, ok := storage.removeAt(foundIdx)
426+
if !ok {
427+
plr.Send(packetCashShopError(opcode.SendCashShopMoveLtoSFailed, constant.CashShopErrorUnknown))
428+
return
429+
}
430+
431+
item := *removedItem
432+
err, givenItem := plr.GiveItem(item)
433+
if err != nil {
434+
if _, restored := storage.addItem(item, item.GetCashSN()); !restored {
435+
log.Println("CRITICAL: Restore to storage failed. Item may be lost. player:", plr.ID, "accountID:", conn.GetAccountID(), "itemID:", item.ID)
436+
} else {
437+
if saveErr := storage.save(); saveErr != nil {
438+
log.Println("Failed to save restored storage:", saveErr)
439+
}
440+
}
441+
plr.Send(packetCashShopError(opcode.SendCashShopMoveLtoSFailed, constant.CashShopErrorCheckFullInventory))
442+
return
443+
}
444+
445+
if saveErr := storage.save(); saveErr != nil {
446+
plr.Send(packetCashShopError(opcode.SendCashShopMoveLtoSFailed, constant.CashShopErrorUnknown))
447+
return
448+
}
449+
450+
plr.Send(packetCashShopMoveLtoSDone(givenItem, targetSlot))
451+
452+
case opcode.RecvCashShopMoveStoL:
453+
// Move from slot (inventory) to locker (storage)
454+
cashItemID := reader.ReadInt64()
455+
invType := reader.ReadByte()
456+
457+
storage, storageErr := server.GetOrLoadStorage(conn)
458+
if storageErr != nil {
459+
log.Println("Failed to get storage:", storageErr)
460+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
461+
return
462+
}
463+
464+
item, itemSlot, findErr := plr.GetItemByCashID(invType, cashItemID)
465+
if findErr != nil {
466+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
467+
return
468+
}
469+
470+
expectedInvType := byte(item.ID / 1000000)
471+
if expectedInvType != invType {
472+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
473+
return
474+
}
475+
476+
takenItem, takeErr := plr.TakeItemForStorage(item.ID, itemSlot, 1, invType)
477+
if takeErr != nil {
478+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
479+
return
480+
}
481+
482+
slotIdx, added := storage.addItemWithCashID(takenItem, takenItem.GetCashSN(), takenItem.GetCashID())
483+
if !added {
484+
if err, _ := plr.GiveItem(takenItem); err != nil {
485+
log.Println("CRITICAL: Failed to return item to player after add failure:", err)
486+
}
487+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorExceededNumberOfCashItems))
488+
return
489+
}
490+
491+
if saveErr := storage.save(); saveErr != nil {
492+
log.Println("Failed to save storage:", saveErr)
493+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
494+
return
495+
}
496+
497+
addedItem, ok := storage.getItemBySlot(int16(slotIdx + 1))
498+
if ok {
499+
plr.Send(packetCashShopMoveStoLDone(*addedItem, conn.GetAccountID()))
500+
} else {
501+
plr.Send(packetCashShopError(opcode.SendCashShopMoveStoLFailed, constant.CashShopErrorUnknown))
502+
}
503+
359504
default:
360505
log.Println("Unknown Cash Shop Packet(", sub, "): ", reader)
361506
}

cashshop/packets.go

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ func packetCashShopSendCSItemInventory(slotType byte, it channel.Item) mpacket.P
119119
func packetCashShopWishList(sns []int32, update bool) mpacket.Packet {
120120
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
121121
if update {
122-
p.WriteByte(0x39)
122+
p.WriteByte(opcode.SendCashShopUpdateWishDone)
123123
} else {
124-
p.WriteByte(0x33)
124+
p.WriteByte(opcode.SendCashShopLoadWishDone)
125125
}
126126
count := 10
127127
for i := 0; i < count; i++ {
@@ -134,6 +134,67 @@ func packetCashShopWishList(sns []int32, update bool) mpacket.Packet {
134134
return p
135135
}
136136

137+
func packetCashShopLoadLocker(storage *CashShopStorage, accountID, characterID int32) mpacket.Packet {
138+
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
139+
p.WriteByte(opcode.SendCashShopLoadLockerDone)
140+
141+
items := storage.getAllItems()
142+
143+
p.WriteInt16(int16(len(items)))
144+
for _, csItem := range items {
145+
p.WriteInt64(csItem.GetCashID())
146+
p.WriteInt32(accountID)
147+
p.WriteInt32(characterID)
148+
p.WriteInt32(csItem.ID)
149+
p.WriteInt32(csItem.GetCashSN())
150+
p.WriteInt16(csItem.GetAmount())
151+
p.WritePaddedString("", 13)
152+
p.WriteInt64(csItem.GetExpireTime())
153+
p.WriteInt64(0) // Padding
154+
}
155+
156+
p.WriteInt16(0) // Gift count
157+
p.WriteInt16(int16(storage.maxSlots))
158+
return p
159+
}
160+
161+
func packetCashShopMoveLtoSDone(item channel.Item, slot int16) mpacket.Packet {
162+
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
163+
p.WriteByte(opcode.SendCashShopMoveLtoSDone)
164+
p.WriteBytes(item.ShortBytes())
165+
return p
166+
}
167+
168+
func packetCashShopMoveStoLDone(csItem channel.Item, accountID int32) mpacket.Packet {
169+
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
170+
p.WriteByte(opcode.SendCashShopMoveStoLDone)
171+
p.WriteInt64(csItem.GetCashID())
172+
p.WriteInt32(accountID)
173+
p.WriteInt32(0)
174+
p.WriteInt32(csItem.ID)
175+
p.WriteInt32(csItem.GetCashSN())
176+
p.WriteInt16(csItem.GetAmount())
177+
p.WritePaddedString("", 13) // GiftName
178+
p.WriteInt64(csItem.GetExpireTime())
179+
p.WriteInt64(0)
180+
return p
181+
}
182+
183+
func packetCashShopBuyDone(csItem channel.Item, accountID, characterID int32) mpacket.Packet {
184+
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
185+
p.WriteByte(opcode.SendCashShopBuyDone)
186+
p.WriteInt64(csItem.GetCashID())
187+
p.WriteInt32(accountID)
188+
p.WriteInt32(characterID)
189+
p.WriteInt32(csItem.ID)
190+
p.WriteInt32(csItem.GetCashSN())
191+
p.WriteInt16(csItem.GetAmount())
192+
p.WritePaddedString("", 13) // GiftName
193+
p.WriteInt64(csItem.GetExpireTime())
194+
p.WriteInt64(0)
195+
return p
196+
}
197+
137198
func packetCashShopWrongCoupon() mpacket.Packet {
138199
p := mpacket.CreateWithOpcode(opcode.SendChannelCSAction)
139200
p.WriteByte(0x40)

cashshop/server.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ func (server *Server) ClientDisconnected(conn mnet.Client) {
6666
return
6767
}
6868

69+
accountID := conn.GetAccountID()
70+
if storage := conn.GetCashShopStorage(); storage != nil {
71+
if cashStorage, ok := storage.(*CashShopStorage); ok {
72+
log.Printf("Saving cash shop storage for account %d on disconnect\n", accountID)
73+
if saveErr := cashStorage.save(); saveErr != nil {
74+
log.Println("Failed to save cash shop storage for account", accountID, ":", saveErr)
75+
}
76+
}
77+
}
78+
6979
plr.Logout()
7080

7181
if remPlrErr := server.players.RemoveFromConn(conn); remPlrErr != nil {
@@ -88,6 +98,8 @@ func (server *Server) ClientDisconnected(conn mnet.Client) {
8898
}
8999

90100
server.world.Send(internal.PacketChannelPlayerDisconnect(plr.ID, plr.Name, 0))
101+
102+
conn.SetCashShopStorage(nil)
91103
}
92104

93105
// CheckpointAll now uses the saver to flush debounced/coalesced deltas for every player.
@@ -130,3 +142,21 @@ func (server *Server) StartAutosave(ctx context.Context) {
130142
scheduleNext()
131143
}
132144
}
145+
146+
// GetOrLoadStorage gets or loads cash shop storage for an account
147+
func (server *Server) GetOrLoadStorage(conn mnet.Client) (*CashShopStorage, error) {
148+
if storage := conn.GetCashShopStorage(); storage != nil {
149+
if cashStorage, ok := storage.(*CashShopStorage); ok {
150+
return cashStorage, nil
151+
}
152+
}
153+
154+
accountID := conn.GetAccountID()
155+
storage := NewCashShopStorage(accountID)
156+
if err := storage.load(); err != nil {
157+
return nil, err
158+
}
159+
160+
conn.SetCashShopStorage(storage)
161+
return storage, nil
162+
}

0 commit comments

Comments
 (0)