diff --git a/network/mhfpacket/msg_mhf_stampcard_stamp.go b/network/mhfpacket/msg_mhf_stampcard_stamp.go index fd0e0ef1e..8d18ae971 100644 --- a/network/mhfpacket/msg_mhf_stampcard_stamp.go +++ b/network/mhfpacket/msg_mhf_stampcard_stamp.go @@ -1,27 +1,25 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfStampcardStamp represents the MSG_MHF_STAMPCARD_STAMP type MsgMhfStampcardStamp struct { - // Field-size accurate. AckHandle uint32 - Unk0 uint16 - Unk1 uint16 - Unk2 uint16 - Unk3 uint16 // Hardcoded 0 in binary - Unk4 uint32 - Unk5 uint32 - Unk6 uint32 - Unk7 uint32 - Unk8 uint32 - Unk9 uint32 + HR uint16 + GR uint16 + Stamps uint16 + Reward1 uint16 + Reward2 uint16 + Item1 uint16 + Item2 uint16 + Quantity1 uint16 + Quantity2 uint16 } // Opcode returns the ID associated with this packet type. @@ -32,16 +30,16 @@ func (m *MsgMhfStampcardStamp) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfStampcardStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint16() - m.Unk1 = bf.ReadUint16() - m.Unk2 = bf.ReadUint16() - m.Unk3 = bf.ReadUint16() - m.Unk4 = bf.ReadUint32() - m.Unk5 = bf.ReadUint32() - m.Unk6 = bf.ReadUint32() - m.Unk7 = bf.ReadUint32() - m.Unk8 = bf.ReadUint32() - m.Unk9 = bf.ReadUint32() + m.HR = bf.ReadUint16() + m.GR = bf.ReadUint16() + m.Stamps = bf.ReadUint16() + _ = bf.ReadUint16() + m.Reward1 = uint16(bf.ReadUint32()) + m.Reward2 = uint16(bf.ReadUint32()) + m.Item1 = uint16(bf.ReadUint32()) + m.Item2 = uint16(bf.ReadUint32()) + m.Quantity1 = uint16(bf.ReadUint32()) + m.Quantity2 = uint16(bf.ReadUint32()) return nil } diff --git a/network/mhfpacket/msg_mhf_update_etc_point.go b/network/mhfpacket/msg_mhf_update_etc_point.go index 5110557ab..c73bf42ea 100644 --- a/network/mhfpacket/msg_mhf_update_etc_point.go +++ b/network/mhfpacket/msg_mhf_update_etc_point.go @@ -1,18 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUpdateEtcPoint represents the MSG_MHF_UPDATE_ETC_POINT type MsgMhfUpdateEtcPoint struct { AckHandle uint32 - Unk0 uint8 - Unk1 uint16 + PointType uint8 + Delta int16 } // Opcode returns the ID associated with this packet type. @@ -23,8 +23,8 @@ func (m *MsgMhfUpdateEtcPoint) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateEtcPoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() - m.Unk1 = bf.ReadUint16() + m.PointType = bf.ReadUint8() + m.Delta = bf.ReadInt16() return nil } diff --git a/patch-schema/etc-points.sql b/patch-schema/etc-points.sql new file mode 100644 index 000000000..88062922f --- /dev/null +++ b/patch-schema/etc-points.sql @@ -0,0 +1,9 @@ +BEGIN; + +ALTER TABLE characters ADD bonus_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE characters ADD daily_quests INT NOT NULL DEFAULT 0; + +ALTER TABLE characters ADD promo_points INT NOT NULL DEFAULT 0; + +END; \ No newline at end of file diff --git a/patch-schema/stampcard.sql b/patch-schema/stampcard.sql new file mode 100644 index 000000000..f2c6b7d10 --- /dev/null +++ b/patch-schema/stampcard.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE characters ADD stampcard INT NOT NULL DEFAULT 0; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index d81d6ece5..0194e18e6 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -1502,25 +1502,74 @@ func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetEtcPoints) - resp := byteframe.NewByteFrame() - resp.WriteUint8(0x3) // Maybe a count of uint32(s)? - resp.WriteUint32(0) // Point bonus quests - resp.WriteUint32(0) // Daily quests - resp.WriteUint32(0) // HS promotion points + var dailyTime time.Time + _ = s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime) + if Time_Current_Adjusted().After(dailyTime) { + s.server.db.Exec("UPDATE characters SET bonus_quests = 0, daily_quests = 0 WHERE id=$1", s.charID) + } + var bonusQuests, dailyQuests, promoPoints uint32 + _ = s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints) + resp := byteframe.NewByteFrame() + resp.WriteUint8(3) // Maybe a count of uint32(s)? + resp.WriteUint32(bonusQuests) + resp.WriteUint32(dailyQuests) + resp.WriteUint32(promoPoints) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateEtcPoint) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + + var column string + switch pkt.PointType { + case 0: + column = "bonus_quests" + case 1: + column = "daily_quests" + case 2: + column = "promo_points" + } + + var value int + err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value) + if err == nil { + if value-int(pkt.Delta) < 0 { + s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID) + } else { + s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID) + } + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfStampcardStamp) - // TODO: Work out where it gets existing stamp count from, its format and then - // update the actual sent values to be correct - doAckBufSucceed(s, pkt.AckHandle, []byte{0x03, 0xe7, 0x03, 0xe7, 0x02, 0x99, 0x02, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x14, 0xf8, 0x69, 0x54}) + bf := byteframe.NewByteFrame() + bf.WriteUint16(pkt.HR) + bf.WriteUint16(pkt.GR) + var stamps uint16 + _ = s.server.db.QueryRow(`SELECT stampcard FROM characters WHERE id = $1`, s.charID).Scan(&stamps) + bf.WriteUint16(stamps) + stamps += pkt.Stamps + bf.WriteUint16(stamps) + s.server.db.Exec(`UPDATE characters SET stampcard = $1 WHERE id = $2`, stamps, s.charID) + if stamps%30 == 0 { + bf.WriteUint16(2) + bf.WriteUint16(pkt.Reward2) + bf.WriteUint16(pkt.Item2) + bf.WriteUint16(pkt.Quantity2) + addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item2, Quantity: pkt.Quantity2}) + } else if stamps%15 == 0 { + bf.WriteUint16(1) + bf.WriteUint16(pkt.Reward1) + bf.WriteUint16(pkt.Item1) + bf.WriteUint16(pkt.Quantity1) + addWarehouseGift(s, "item", mhfpacket.WarehouseStack{ItemID: pkt.Item1, Quantity: pkt.Quantity1}) + } else { + bf.WriteBytes(make([]byte, 8)) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfStampcardPrize(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 2a806305b..c8e84e087 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -52,20 +52,20 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) { s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err)) } - var bondBonus, pointBonus, dailyQuests uint32 + var bondBonus, bonusQuests, dailyQuests uint32 bf := byteframe.NewByteFrame() if t.After(dailyTime) { addPointNetcafe(s, 5) - s.server.db.Exec("UPDATE characters SET daily_time=$1 WHERE id=$2", midday, s.charID) + bondBonus = 5 // Bond point bonus quests + bonusQuests = 3 // HRP bonus quests? + dailyQuests = 1 // Daily quests + s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID) bf.WriteBool(true) // Success? - bondBonus = 5 // Bond point bonus quests - pointBonus = 3 // HRP bonus quests? - dailyQuests = 1 // Daily quests } else { bf.WriteBool(false) } bf.WriteUint32(bondBonus) - bf.WriteUint32(pointBonus) + bf.WriteUint32(bonusQuests) bf.WriteUint32(dailyQuests) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 76e2db778..5ced41733 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -1,6 +1,7 @@ package channelserver import ( + "encoding/hex" "fmt" "io" "os" @@ -113,16 +114,32 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } vsQuestItems := []uint16{1580, 1581, 1582, 1583, 1584, 1585, 1587, 1588, 1589, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604} + vsQuestBets := []struct { + IsTicket bool + Quantity uint32 + }{ + {true, 5}, + {false, 1000}, + {false, 5000}, + {false, 10000}, + } + + data, _ := hex.DecodeString("7F2301CF7CD17F23111111007E0F7B357F23111111007F407CD07F23111111007E0F7CD77F23111111007E0F7CD67F23111111007E0F7B197F23111111007F277B1D7F23111111007F277B617F23111111007F207BAD7F23111111007F267BB17F23111111007F267BB57F23111111007F217BA07F23111111007F237B257F23111111007F2B7B227F23111111007F2272857F23111111007F237B277F23111111007F477B207F23111111007F477F027F23111111007F227F737F23111111007F227F127F23111111007F227F377F23111111007F227F187F23111111007F227F167F23111111007F227F397F23111111007F227F7D7F23111111007F227F0B7F23111111007F227F387F23111111007F227F607F23111111007F227CDC7F23111111007F2B7B237F23111111007FB572847F23111111007F23728B7F23111111007F23728A7F23111111007F2372897F23111111007F2372887F23111111007F23728F7F23111111007F23728E7F23111111007F23728D7F23111111007F23728C7F23111111007F2372937F23111111007F2372927F23111111007F2372917F23111111007F2372E37F23111111007F2372E27F23111111007F237B1B7F23111111007F227B1A7F23111111007F2272E17F23111111007F2372E07F23111111007F2373F57F23111111007FEB73F47F23111111007FEB73FB7F23111111007FEB72E77F23111111007F2372E67F23111111007F2372E57F23111111007F2372E47F23111111007F2372EB7F23111111007F2372EA7F23111111007F237CDF7F23111111007F477CDE7F23111111007EB37B087F23111111007F7972E97F23111111007F2373FA7F23111111007FEB73F97F23111111007FEB73F87F23111111007FEB73FF7F23111111007FEB73FE7F23111111007FEB73FD7F23111111007FEB73FC7F23111111007FEB73C37F23111111007FEB73C27F23111111007FEB73C17F23111111007FEB73C07F23111111007FEB73C77F23111111007FEB73C67F23111111007FEB73C57F23111111007FEB73C47F23111111007FEB73CB7F23111111007FEB73CA7F23111111007FEB73C97F23111111007FEB73C87F23111111007FEB73CF7F23111111007FEB73CE7F23111111007FEB73CD7F23111111007FEB73CC7F23111111007FEB73D37F23111111007E0F73D27F23111111007E0F73D17F23111111007E0F73D07F23111111007E0F73D77F23111111007E0F73D67F23111111007E0F73D57F23111111007E0F73D47F23111111007E0F73DB7F23111111007E0F73DA7F23111111007E0F73D97F23111111007E0F73D87F23111111007E0F73DF7F23111111007E0F73DE7F23111111007E0F73DD7F23111111007E0F73DC7F23111111007E0F72237F23111111007E0F72227F23111111007E0F72217F23111111007E0F72207F23111111007E0F72277F23111111007E0F72E87F23111111007F2372EF7F23111111007F2372267F23111111007E0F7B137F23111111007E0F7B5A7F23111111007FEB72517F23111111007F4772257F23111111007E0F72247F23111111007E0F722B7F23111111007E0F722A7F23111111007E0F7B047F2311111100BC737B0B7F2311111100BC737B0A7F23111111001E8B7B097F23111111001E8B7CD57F23111111007FEB7B3F7F23111111007FEB7B0F7F23111111007F737B0E7F23111111007F657CD47F23111111007FEB7B387F23111111007FEB72507F23111111007F4772577F23111111007F4772567F23111111007F4772557F23111111007F4772547F23111111007F47725B7F23111111007F47725A7F23111111007F4772297F23111111007F4772287F23111111007F47722F7F23111111007F47722E7F23111111007F47722D7F23111111007F47722C7F23111111007F4772337F23111111007F4772327F23111111007F4772317F23111111007F4772307F23111111007F4772377F23111111007F4772367F23111111007F4772357F23111111007F4772347F23111111007F477B1E7F23111111007F2272597F23111111007F47723B7F23111111007F47723A7F23111111007F477B627F23111111007F2272587F23111111007F4772397F23111111007F477B147F23111111007F227B187F23111111007F217B1F7F23111111007F297B1C7F23111111007F217B637F23111111007F297B607F23111111007F217B677F23111111007F297B647F23111111007F217B6B7F23111111007F297B687F23111111007F217B6F7F23111111007F297B6C7F23111111007F217B737F23111111007F2972387F23111111007F47723F7F23111111007F47723E7F23111111007F47723D7F23111111007F47723C7F23111111007F4772037F23111111007F4772027F23111111007F4772017F23111111007F4772007F23111111007F4772077F23111111007F4772067F23111111007F4772057F23111111007F4772047F23111111007F47720B7F23111111007F47720A7F23111111007F4772097F23111111007F4772087F23111111007F47720F7F23111111007F47720E7F23111111007F47720D7F23111111007F47720C7F23111111007F4772137F23111111007F4772127F23111111007F4772117F23111111007F4772107F23111111007F4772177F23111111007F4772167F23111111007F4772157F23111111007F4772147F23111111007F47721B7F23111111007F47721A7F23111111007F4772197F23111111007F4772187F23111111007F47721F7F23111111007F47721E7F23111111007F47725F7F23111111007F47721D7F23111111007F47721C7F23111111007F4772637F23111111007F4772627F23111111007F4772617F23111111007F4772607F23111111007F4772677F23111111007F4772667F23111111007F4772657F23111111007F4772647F23111111007F47726B7F23111111007F47726A7F23111111007F4772697F23111111007F47727B7F23111111007F47727A7F23111111007F4772797F23111111007F4772787F23111111007F47727F7F23111111007F47727E7F23111111007F47727D7F23111111007F47727C7F23111111007F4772437F23111111007F4772427F23111111007F4772417F23111111007F4772407F23111111007F4772477F23111111007F477B127F23111111007E0F7B117F23111111007E0F7BAB7F23111111007E0F725E7F23111111007F47725D7F23111111007F4772AF7F23111111007F4772AE7F23111111007F477B597F23111111007F2372AD7F23111111007F477B587F23111111007F2372AC7F23111111007F477B217F23111111007CC47B157F23111111007F237BA37F23111111007B497BA97F23111111007F227BA87F23111111007F207BAF7F23111111007F267BAE7F23111111007F227BAC7F23111111007F227BB37F23111111007F207BB27F23111111007F207BB07F23111111007F227BB77F23111111007F227BB67F23111111007F22738A7F23111111007F2173897F23111111007F2173887F23111111007F21738F7F23111111007F217B5B7F23111111007F23738E7F23111111007F21738D7F23111111007F217B667F23111111007F217B657F23111111007F267B6A7F23111111007F217B697F23111111007F267B6E7F23111111007F217B6D7F23111111007F267BB47F23111111007F277BBB7F23111111007F297BBA7F23111111007F277BB97F23111111007F297BB87F23111111007F2172B37F23111111007F4772B27F23111111007F4772B17F23111111007F4772B07F23111111007F4772B77F23111111007F4772B67F23111111007F4772B57F23111111007F4772B47F23111111007F4772BB7F23111111007F4772F97F23111111007F2372F87F23111111007F237BA27F23111111007F237B267F23111111007F29749B7F23111111007ED7749A7F23111111007ED774997F23111111007ED774987F23111111007ED7749F7F23111111007ED7749E7F23111111007ED77B247F23111111007F47749D7F23111111007ED7749C7F23111111007ED774E37F23111111007ED774E27F23111111007ED774E17F23111111007ED774E07F23111111007ED774E77F23111111007ED774E67F23111111007ED774E57F23111111007ED774E47F23111111007ED774EB7F23111111007ED774EA7F23111111007ED774E97F23111111007ED774E87F23111111007ED774EF7F23111111007ED774EE7F23111111007ED774ED7F23111111007ED774EC7F23111111007ED77B717F23111111007F2374F37F23111111007ED774F27F23111111007ED774F17F23111111007ED77BA17F23111111007F2372FF7F23111111007F2373777F23111111007F2372FE7F23111111007F2373767F23111111007F2373757F23111111007F2373747F23111111007F23737B7F23111111007F23737A7F23111111007F2373797F23111111007F2373787F23111111007F23737F7F23111111007F23737E7F23111111007F23737D7F23111111007F23737C7F23111111007F2373437F23111111007F23734D7F23111111007F23734C7F23111111007F2373537F23111111007F2373527F23111111007F2373517F23111111007F2373507F23111111007F2373577F23111111007F2373567F23111111007F2373557F23111111007F2373547F23111111007F23735B7F23111111007F23735A7F23111111007F2373597F23111111007F2372FD7F23111111007F2372FC7F23111111007F2372C37F23111111007F2372C27F23111111007F2372C17F23111111007F2372C07F23111111007F2372C77F23111111007F2372C67F23111111007F2372C57F23111111007F2372D77F23111111007F2372D67F23111111007F2372D57F23111111007F2372D47F23111111007F2372DB7F23111111007F2372DA7F23111111007F2372D97F23111111007F2372D87F23111111007F2372DF7F23111111007F2372DE7F23111111007F2372DD7F23111111007F2372DC7F23111111007F2371237F23111111007F237B5E7F23111111007F3774F07F23111111007ED774F77F23111111007ED774F67F23111111007ED77BA57F23111111007F2274F57F23111111007ED774F47F23111111007ED774FB7F23111111007ED774FA7F23111111007ED774F97F23111111007ED774F87F23111111007ED774FF7F23111111007ED774FE7F23111111007ED774FD7F23111111007ED774FC7F23111111007ED774C37F23111111007ED774C27F23111111007ED774C17F23111111007ED774C07F23111111007ED774C77F23111111007ED774C67F23111111007ED774C57F23111111007ED774C47F23111111007ED774CB7F23111111007ED774CA7F23111111007ED774C97F23111111007ED774C87F23111111007ED774CF7F23111111007EB374CE7F23111111007EB374CD7F23111111007EB374CC7F23111111007EB374D37F23111111007EB374D27F23111111007EB374D17F23111111007EB374D07F23111111007EB374D77F23111111007EB374D67F23111111007EB374D57F23111111007EB374D47F23111111007EB374DB7F23111111007EB37BBF7F23111111007F2673257F23111111007EB373247F23111111007EB3732B7F23111111007EB3732A7F23111111007EB373297F23111111007EB373287F23111111007EB3732F7F23111111007EB3732E7F23111111007EB3732D7F23111111007EB3732C7F23111111007EB373337F23111111007EB373327F23111111007EB373317F23111111007EB373037F23111111007CCB73027F23111111007CCB73017F23111111007CCB73007F23111111007CCB73077F23111111007CCB73067F23111111007CCB73057F23111111007CCB73047F23111111007CCB730B7F23111111007CCB730A7F23111111007CCB73097F23111111007CCB73087F23111111007CCB730F7F23111111007CCB73197F23111111007CCB73187F23111111007CCB731F7F23111111007CCB731E7F23111111007CCB731D7F23111111007CCB731C7F23111111007CCB73637F23111111007CCB73627F23111111007CCB73617F23111111007CCB73607F23111111007CCB73677F23111111007CCB73667F23111111007CCB73657F23111111007CCB73AB7F23111111007F2173AA7F23111111007F2173A97F23111111007F2173A87F23111111007F2173AF7F23111111007F2173AE7F23111111007F2173AD7F23111111007F2173AC7F23111111007F2173B37F23111111007F2173B27F23111111007F2173B17F23111111007F2173B07F23111111007F2173B77F23111111007F2173817F23111111007F2173807F23111111007F2173877F23111111007F2173867F23111111007F2173857F23111111007F2173847F23111111007F21738B7F23111111007F21") + bf.WriteBytes(data) - bf.WriteUint16(0) // Unk - bf.WriteUint16(0) // Unk bf.WriteUint16(uint16(len(vsQuestItems))) - bf.WriteUint32(0) // Unk + bf.WriteUint32(uint32(len(vsQuestBets))) bf.WriteUint16(0) // Unk for i := range vsQuestItems { bf.WriteUint16(vsQuestItems[i]) } + for i := range vsQuestBets { + bf.WriteBool(vsQuestBets[i].IsTicket) + bf.WriteUint8(9) + bf.WriteUint16(7) + bf.WriteUint32(vsQuestBets[i].Quantity) + } bf.WriteUint16(totalCount) bf.WriteUint16(pkt.Offset) diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 90ecdec99..bb5f41ff7 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -158,7 +158,9 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { if s.stageID == "" { s.stageMoveStack.Set(pkt.StageID) } else { + s.stage.Lock() s.stage.reservedClientSlots[s.charID] = false + s.stage.Unlock() s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Lock() }