Merge branch 'main' into feature/discord-login

This commit is contained in:
Matthew
2023-11-29 10:52:01 -08:00
committed by GitHub
12 changed files with 183 additions and 92 deletions

View File

@@ -5,6 +5,42 @@ INSERT INTO public.shop_items
VALUES VALUES
(5,5,16516,100,1,0,0,1,0,0,0,0), (5,5,16516,100,1,0,0,1,0,0,0,0),
(5,5,16517,100,1,0,0,1,0,0,0,0), (5,5,16517,100,1,0,0,1,0,0,0,0),
(6,5,9958,3,3,1,0,0,0,0,0,0),
(6,5,1897,3,1,1,0,0,0,0,0,0),
(6,5,8889,3,1,0,0,1,0,0,0,0),
(6,5,6176,3,6,1,0,0,0,0,0,0),
(6,5,1472,3,10,1,0,0,0,0,0,0),
(6,5,7280,3,3,0,0,1,0,0,0,0),
(6,5,8027,3,30,1,0,0,0,0,0,0),
(6,5,8028,3,30,1,0,0,0,0,0,0),
(6,5,8029,3,30,1,0,0,0,0,0,0),
(6,5,8026,3,30,1,0,0,0,0,0,0),
(6,5,8030,3,30,1,0,0,0,0,0,0),
(6,5,4353,3,30,1,0,0,0,0,0,0),
(6,5,4354,3,30,1,0,0,0,0,0,0),
(6,5,4355,3,30,1,0,0,0,0,0,0),
(6,5,4356,3,30,1,0,0,0,0,0,0),
(6,5,4357,3,30,1,0,0,0,0,0,0),
(6,5,4745,3,30,1,0,0,0,0,0,0),
(6,5,4746,3,30,1,0,0,0,0,0,0),
(6,5,4747,3,30,1,0,0,0,0,0,0),
(6,5,4748,3,30,1,0,0,0,0,0,0),
(6,5,4749,3,30,1,0,0,0,0,0,0),
(6,5,5122,3,30,1,0,0,0,0,0,0),
(6,5,5123,3,30,1,0,0,0,0,0,0),
(6,5,5124,3,30,1,0,0,0,0,0,0),
(6,5,5125,3,30,1,0,0,0,0,0,0),
(6,5,5126,3,30,1,0,0,0,0,0,0),
(6,5,5795,3,30,1,0,0,0,0,0,0),
(6,5,5796,3,30,1,0,0,0,0,0,0),
(6,5,5797,3,30,1,0,0,0,0,0,0),
(6,5,5798,3,30,1,0,0,0,0,0,0),
(6,5,5799,3,30,1,0,0,0,0,0,0),
(6,5,6168,3,30,1,0,0,0,0,0,0),
(6,5,6169,3,30,1,0,0,0,0,0,0),
(6,5,6170,3,30,1,0,0,0,0,0,0),
(6,5,6171,3,30,1,0,0,0,0,0,0),
(6,5,6172,3,30,1,0,0,0,0,0,0),
(7,0,13190,10,1,0,0,0,0,0,0,0), (7,0,13190,10,1,0,0,0,0,0,0,0),
(7,0,1662,10,1,0,0,0,0,0,0,0), (7,0,1662,10,1,0,0,0,0,0,0,0),
(7,0,10179,100,1,0,0,0,0,0,0,0); (7,0,10179,100,1,0,0,0,0,0,0,0);

View File

@@ -14,6 +14,7 @@
"ClientMode": "ZZ", "ClientMode": "ZZ",
"QuestCacheExpiry": 300, "QuestCacheExpiry": 300,
"ProxyPort": 0, "ProxyPort": 0,
"CommandPrefix": "!",
"DevMode": true, "DevMode": true,
"DevModeOptions": { "DevModeOptions": {
"AutoCreateAccount": true, "AutoCreateAccount": true,
@@ -30,7 +31,7 @@
"QuestDebugTools": false, "QuestDebugTools": false,
"EarthStatusOverride": 0, "EarthStatusOverride": 0,
"EarthIDOverride": 0, "EarthIDOverride": 0,
"EarthMonsterOverride": 0, "EarthMonsterOverride": [0, 0, 0, 0],
"SaveDumps": { "SaveDumps": {
"Enabled": true, "Enabled": true,
"OutputDir": "save-backups" "OutputDir": "save-backups"
@@ -82,32 +83,44 @@
}, },
"Commands": [ "Commands": [
{ {
"Name": "Help",
"Enabled": true,
"Description": "Show enabled chat commands",
"Prefix": "help"
}, {
"Name": "Rights", "Name": "Rights",
"Enabled": false, "Enabled": false,
"Description": "Overwrite the Rights value on your account",
"Prefix": "rights" "Prefix": "rights"
}, { }, {
"Name": "Raviente", "Name": "Raviente",
"Enabled": true, "Enabled": true,
"Description": "Various Raviente siege commands",
"Prefix": "ravi" "Prefix": "ravi"
}, { }, {
"Name": "Teleport", "Name": "Teleport",
"Enabled": false, "Enabled": false,
"Description": "Teleport to specified coordinates",
"Prefix": "tele" "Prefix": "tele"
}, { }, {
"Name": "Reload", "Name": "Reload",
"Enabled": true, "Enabled": true,
"Description": "Reload all players in your Land",
"Prefix": "reload" "Prefix": "reload"
}, { }, {
"Name": "KeyQuest", "Name": "KeyQuest",
"Enabled": false, "Enabled": false,
"Description": "Overwrite your HR Key Quest progress",
"Prefix": "kqf" "Prefix": "kqf"
}, { }, {
"Name": "Course", "Name": "Course",
"Enabled": true, "Enabled": true,
"Description": "Toggle Courses on your account",
"Prefix": "course" "Prefix": "course"
}, { }, {
"Name": "PSN", "Name": "PSN",
"Enabled": true, "Enabled": true,
"Description": "Link a PlayStation Network ID to your account",
"Prefix": "psn" "Prefix": "psn"
}, { }, {
"Name": "Discord", "Name": "Discord",

View File

@@ -81,6 +81,7 @@ type Config struct {
RealClientMode Mode RealClientMode Mode
QuestCacheExpiry int // Number of seconds to keep quest data cached QuestCacheExpiry int // Number of seconds to keep quest data cached
ProxyPort uint16 // Forces the game to connect to a channel server proxy ProxyPort uint16 // Forces the game to connect to a channel server proxy
CommandPrefix string // The prefix for commands
DevMode bool DevMode bool
DevModeOptions DevModeOptions DevModeOptions DevModeOptions
@@ -111,7 +112,7 @@ type DevModeOptions struct {
QuestDebugTools bool // Enable various quest debug logs QuestDebugTools bool // Enable various quest debug logs
EarthStatusOverride int32 EarthStatusOverride int32
EarthIDOverride int32 EarthIDOverride int32
EarthMonsterOverride int32 EarthMonsterOverride []int32
SaveDumps SaveDumpOptions SaveDumps SaveDumpOptions
} }
@@ -175,6 +176,7 @@ type DiscordRealTime struct {
type Command struct { type Command struct {
Name string Name string
Enabled bool Enabled bool
Description string
Prefix string Prefix string
} }

View File

@@ -14,7 +14,7 @@ type MsgMhfEnumerateShop struct {
AckHandle uint32 AckHandle uint32
ShopType uint8 // 1 running gachas, 10 normal shop extensions, 8 Diva Defense shop ShopType uint8 // 1 running gachas, 10 normal shop extensions, 8 Diva Defense shop
ShopID uint32 ShopID uint32
Unk2 uint16 // 00 80 running gachas, 00 20 normal shop Limit uint16
Unk3 uint8 Unk3 uint8
Unk4 uint8 Unk4 uint8
Unk5 uint32 Unk5 uint32
@@ -30,7 +30,7 @@ func (m *MsgMhfEnumerateShop) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clie
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.ShopType = bf.ReadUint8() m.ShopType = bf.ReadUint8()
m.ShopID = bf.ReadUint32() m.ShopID = bf.ReadUint32()
m.Unk2 = bf.ReadUint16() m.Limit = bf.ReadUint16()
m.Unk3 = bf.ReadUint8() m.Unk3 = bf.ReadUint8()
if _config.ErupeConfig.RealClientMode >= _config.G2 { if _config.ErupeConfig.RealClientMode >= _config.G2 {
m.Unk4 = bf.ReadUint8() m.Unk4 = bf.ReadUint8()

View File

@@ -3,13 +3,22 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfPostSeibattle represents the MSG_MHF_POST_SEIBATTLE // MsgMhfPostSeibattle represents the MSG_MHF_POST_SEIBATTLE
type MsgMhfPostSeibattle struct{} type MsgMhfPostSeibattle struct {
AckHandle uint32
Unk0 uint8
Unk1 uint8
Unk2 uint32
Unk3 uint8
Unk4 uint16
Unk5 uint16
Unk6 uint8
}
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfPostSeibattle) Opcode() network.PacketID { func (m *MsgMhfPostSeibattle) Opcode() network.PacketID {
@@ -18,7 +27,15 @@ func (m *MsgMhfPostSeibattle) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfPostSeibattle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfPostSeibattle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
return errors.New("NOT IMPLEMENTED") m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint32()
m.Unk3 = bf.ReadUint8()
m.Unk4 = bf.ReadUint16()
m.Unk5 = bf.ReadUint16()
m.Unk6 = bf.ReadUint8()
return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.

View File

@@ -992,10 +992,17 @@ func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthStatusOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride) bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthIDOverride)
bf.WriteInt32(s.server.erupeConfig.DevModeOptions.EarthMonsterOverride) for i, m := range s.server.erupeConfig.DevModeOptions.EarthMonsterOverride {
bf.WriteInt32(0) if _config.ErupeConfig.RealClientMode <= _config.G9 {
bf.WriteInt32(0) if i == 3 {
bf.WriteInt32(0) break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -1195,7 +1202,10 @@ func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
doAckEarthSucceed(s, pkt.AckHandle, data) doAckEarthSucceed(s, pkt.AckHandle, data)
} }
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostSeibattle)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -85,7 +85,7 @@ func sendServerChatMessage(s *Session, message string) {
} }
func parseChatCommand(s *Session, command string) { func parseChatCommand(s *Session, command string) {
args := strings.Split(command[1:], " ") args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
switch args[0] { switch args[0] {
case commands["PSN"].Prefix: case commands["PSN"].Prefix:
if commands["PSN"].Enabled { if commands["PSN"].Enabled {
@@ -344,6 +344,15 @@ func parseChatCommand(s *Session, command string) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken)) sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDiscordSuccess"], discordToken))
} else { } else {
sendDisabledCommandMessage(s, commands["Discord"]) sendDisabledCommandMessage(s, commands["Discord"])
case commands["Help"].Prefix:
if commands["Help"].Enabled {
for _, command := range commands {
if command.Enabled {
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
}
}
} else {
sendDisabledCommandMessage(s, commands["Help"])
} }
} }
} }
@@ -418,7 +427,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
bf.SetLE() bf.SetLE()
chatMessage := &binpacket.MsgBinChat{} chatMessage := &binpacket.MsgBinChat{}
chatMessage.Parse(bf) chatMessage.Parse(bf)
if strings.HasPrefix(chatMessage.Message, "!") { if strings.HasPrefix(chatMessage.Message, s.server.erupeConfig.CommandPrefix) {
parseChatCommand(s, chatMessage.Message) parseChatCommand(s, chatMessage.Message)
return return
} }

View File

@@ -72,7 +72,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
var timestamps []uint32 var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 { if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 { if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
if s.server.erupeConfig.RealClientMode <= _config.Z1 { if s.server.erupeConfig.RealClientMode >= _config.Z2 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
} else { } else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
@@ -84,7 +84,7 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
timestamps = generateDivaTimestamps(s, start, false) timestamps = generateDivaTimestamps(s, start, false)
} }
if s.server.erupeConfig.RealClientMode <= _config.Z1 { if s.server.erupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint32(id) bf.WriteUint32(id)
} }
for i := range timestamps { for i := range timestamps {

View File

@@ -262,66 +262,55 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset) pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset)
// TODO: Backwards compatibility for DecoMyset var temp []byte
if s.server.erupeConfig.RealClientMode < _config.ZZ { err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&temp)
if err != nil {
s.logger.Error("Failed to load decomyset", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
// https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf
var loadData []byte // Version handling
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData) var size uint
if err != nil { if s.server.erupeConfig.RealClientMode >= _config.G10 {
s.logger.Error("Failed to load decomyset", zap.Error(err)) size = 76
bf.WriteUint8(1)
} else { } else {
numSets := bf.ReadUint8() // sets being written size = 68
// empty save bf.WriteUint8(0)
if len(loadData) == 0 {
loadData = []byte{0x01, 0x00}
} }
savedSets := loadData[1] // existing saved sets // Handle nil data
// no sets, new slice with just first 2 bytes for appends later if len(temp) == 0 {
if savedSets == 0 { temp = append(bf.Data(), uint8(0))
loadData = []byte{0x01, 0x00}
} }
for i := 0; i < int(numSets); i++ {
writeSet := bf.ReadUint16() // Build a map of set data
dataChunk := bf.ReadBytes(76) sets := make(map[uint16][]byte)
setBytes := append([]byte{uint8(writeSet >> 8), uint8(writeSet & 0xff)}, dataChunk...) oldSets := byteframe.NewByteFrameFromBytes(temp[2:])
for x := 0; true; x++ { for i := uint8(0); i < temp[1]; i++ {
if x == int(savedSets) { index := oldSets.ReadUint16()
// appending set sets[index] = oldSets.ReadBytes(size)
if loadData[len(loadData)-1] == 0x10 {
// sanity check for if there was a messy manual import
loadData = append(loadData[:len(loadData)-2], setBytes...)
} else {
loadData = append(loadData, setBytes...)
} }
savedSets++
break // Overwrite existing sets
newSets := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[2:])
for i := uint8(0); i < pkt.RawDataPayload[1]; i++ {
index := newSets.ReadUint16()
sets[index] = newSets.ReadBytes(size)
} }
currentSet := loadData[3+(x*78)]
if int(currentSet) == int(writeSet) { // Serialise the set data
// replacing a set bf.WriteUint8(uint8(len(sets)))
loadData = append(loadData[:2+(x*78)], append(setBytes, loadData[2+((x+1)*78):]...)...) for u, b := range sets {
break bf.WriteUint16(u)
} else if int(currentSet) > int(writeSet) { bf.WriteBytes(b)
// inserting before current set
loadData = append(loadData[:2+((x)*78)], append(setBytes, loadData[2+((x)*78):]...)...)
savedSets++
break
} }
}
loadData[1] = savedSets // update set count dumpSaveData(s, bf.Data(), "decomyset")
} s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", bf.Data(), s.charID)
dumpSaveData(s, loadData, "decomyset") doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
if err != nil {
s.logger.Error("Failed to save decomyset", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} }
type Title struct { type Title struct {

View File

@@ -10,13 +10,13 @@ import (
type ShopItem struct { type ShopItem struct {
ID uint32 `db:"id"` ID uint32 `db:"id"`
ItemID uint16 `db:"item_id"` ItemID uint32 `db:"item_id"`
Cost uint32 `db:"cost"` Cost uint32 `db:"cost"`
Quantity uint16 `db:"quantity"` Quantity uint16 `db:"quantity"`
MinHR uint16 `db:"min_hr"` MinHR uint16 `db:"min_hr"`
MinSR uint16 `db:"min_sr"` MinSR uint16 `db:"min_sr"`
MinGR uint16 `db:"min_gr"` MinGR uint16 `db:"min_gr"`
StoreLevel uint16 `db:"store_level"` StoreLevel uint8 `db:"store_level"`
MaxQuantity uint16 `db:"max_quantity"` MaxQuantity uint16 `db:"max_quantity"`
UsedQuantity uint16 `db:"used_quantity"` UsedQuantity uint16 `db:"used_quantity"`
RoadFloors uint16 `db:"road_floors"` RoadFloors uint16 `db:"road_floors"`
@@ -61,21 +61,32 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
bf.WriteUint16(uint16(len(items))) bf.WriteUint16(uint16(len(items)))
bf.WriteUint16(uint16(len(items))) bf.WriteUint16(uint16(len(items)))
for _, item := range items { for _, item := range items {
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint32(item.ID) bf.WriteUint32(item.ID)
bf.WriteUint16(0) }
bf.WriteUint16(item.ItemID) bf.WriteUint32(item.ItemID)
bf.WriteUint32(item.Cost) bf.WriteUint32(item.Cost)
bf.WriteUint16(item.Quantity) bf.WriteUint16(item.Quantity)
bf.WriteUint16(item.MinHR) bf.WriteUint16(item.MinHR)
bf.WriteUint16(item.MinSR) bf.WriteUint16(item.MinSR)
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.MinGR) bf.WriteUint16(item.MinGR)
bf.WriteUint16(item.StoreLevel) }
bf.WriteUint8(0) // Unk
bf.WriteUint8(item.StoreLevel)
if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.MaxQuantity) bf.WriteUint16(item.MaxQuantity)
bf.WriteUint16(item.UsedQuantity) bf.WriteUint16(item.UsedQuantity)
}
if _config.ErupeConfig.RealClientMode == _config.Z1 {
bf.WriteUint8(uint8(item.RoadFloors))
bf.WriteUint8(uint8(item.RoadFatalis))
} else if _config.ErupeConfig.RealClientMode >= _config.Z2 {
bf.WriteUint16(item.RoadFloors) bf.WriteUint16(item.RoadFloors)
bf.WriteUint16(item.RoadFatalis) bf.WriteUint16(item.RoadFatalis)
} }
} }
}
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem { func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
var items []ShopItem var items []ShopItem
@@ -241,6 +252,9 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
case 10: // Item shop, 0-8 case 10: // Item shop, 0-8
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
items := getShopItems(s, pkt.ShopType, pkt.ShopID) items := getShopItems(s, pkt.ShopType, pkt.ShopID)
if len(items) > int(pkt.Limit) {
items = items[:pkt.Limit]
}
writeShopItems(bf, items) writeShopItems(bf, items)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -171,8 +171,8 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
// Transfer back to the saved stage ID before the previous move or enter. // Transfer back to the saved stage ID before the previous move or enter.
backStage, err := s.stageMoveStack.Pop() backStage, err := s.stageMoveStack.Pop()
if err != nil { if backStage == "" || err != nil {
panic(err) backStage = "sl1Ns200p0a0u0"
} }
if _, exists := s.stage.reservedClientSlots[s.charID]; exists { if _, exists := s.stage.reservedClientSlots[s.charID]; exists {

View File

@@ -126,8 +126,9 @@ func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills) s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID) s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
case 7: case 1, 7:
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID) // This might give too much TSP? No idea what the rate is supposed to be
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=COALESCE(trp, 0)+$2, tsp=COALESCE(tsp, 0)+$3, block1=COALESCE(block1, 0)+$4 WHERE char_id=$5`, pkt.TR, pkt.TRP, pkt.Cost, pkt.Block1, s.charID)
} }
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }