diff --git a/data/XML/mounts.xml b/data/XML/mounts.xml index 625190c465..8f4e3a653d 100644 --- a/data/XML/mounts.xml +++ b/data/XML/mounts.xml @@ -102,4 +102,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml index e6c43299de..3e0a5a6138 100644 --- a/data/XML/outfits.xml +++ b/data/XML/outfits.xml @@ -56,7 +56,56 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113,4 +162,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/items/items.otb b/data/items/items.otb index da589f23e0..bf21c3215d 100644 Binary files a/data/items/items.otb and b/data/items/items.otb differ diff --git a/data/items/items.xml b/data/items/items.xml index c7c877da76..b095032d07 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -7,6 +7,7 @@ + @@ -11515,7 +11516,7 @@ - + @@ -17346,15 +17347,43 @@ - + - + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + @@ -17366,7 +17395,19 @@ - + + + + + + + + + + + + + @@ -37367,4 +37408,3782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/migrations/30.lua b/data/migrations/30.lua index d0ffd9c0cb..e66f37992e 100644 --- a/data/migrations/30.lua +++ b/data/migrations/30.lua @@ -1,3 +1,12 @@ function onUpdateDatabase() - return false + print("> Updating database to version 30 (mount colors)") + db.query([[ + ALTER TABLE `players` + ADD COLUMN `lookmount` int DEFAULT 0 NOT NULL AFTER `lookaddons`, + ADD COLUMN `lookmounthead` int DEFAULT 0 NOT NULL AFTER `lookmount`, + ADD COLUMN `lookmountbody` int DEFAULT 0 NOT NULL AFTER `lookmounthead`, + ADD COLUMN `lookmountlegs` int DEFAULT 0 NOT NULL AFTER `lookmountbody`, + ADD COLUMN `lookmountfeet` int DEFAULT 0 NOT NULL AFTER `lookmountlegs`; + ]]) + return true end diff --git a/data/migrations/31.lua b/data/migrations/31.lua new file mode 100644 index 0000000000..d0ffd9c0cb --- /dev/null +++ b/data/migrations/31.lua @@ -0,0 +1,3 @@ +function onUpdateDatabase() + return false +end diff --git a/data/talkactions/scripts/attributes.lua b/data/talkactions/scripts/attributes.lua index 2c8b21f3f7..ec47cfa568 100644 --- a/data/talkactions/scripts/attributes.lua +++ b/data/talkactions/scripts/attributes.lua @@ -8,19 +8,19 @@ function onSay(player, words, param) local tile = Tile(position) if not tile then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is no tile in front of you.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "There is no tile in front of you.") return false end local thing = tile:getTopVisibleThing(player) if not thing then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "There is an empty tile in front of you.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "There is an empty tile in front of you.") return false end local separatorPos = param:find(',') if not separatorPos then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Usage: %s attribute, value.", words)) + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Usage: %s attribute, value.", words)) return false end @@ -30,19 +30,19 @@ function onSay(player, words, param) if thing:isItem() then local attributeId = Game.getItemAttributeByName(attribute) if attributeId == ITEM_ATTRIBUTE_NONE then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Invalid attribute name.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Invalid attribute name.") return false end if not thing:setAttribute(attribute, value) then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Could not set attribute.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Could not set attribute.") return false end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Attribute %s set to: %s", attribute, thing:getAttribute(attributeId))) + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Attribute %s set to: %s", attribute, thing:getAttribute(attributeId))) position:sendMagicEffect(CONST_ME_MAGIC_GREEN) else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Thing in front of you is not supported.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Thing in front of you is not supported.") return false end end diff --git a/data/talkactions/scripts/closeserver.lua b/data/talkactions/scripts/closeserver.lua index 2f7c95ec3e..b09af02f2e 100644 --- a/data/talkactions/scripts/closeserver.lua +++ b/data/talkactions/scripts/closeserver.lua @@ -11,7 +11,7 @@ function onSay(player, words, param) Game.setGameState(GAME_STATE_SHUTDOWN) else Game.setGameState(GAME_STATE_CLOSED) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now closed.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server is now closed.") end return false end diff --git a/data/talkactions/scripts/force_raid.lua b/data/talkactions/scripts/force_raid.lua index ebdf16230d..99dd2e9a4f 100644 --- a/data/talkactions/scripts/force_raid.lua +++ b/data/talkactions/scripts/force_raid.lua @@ -11,9 +11,9 @@ function onSay(player, words, param) local returnValue = Game.startRaid(param) if returnValue ~= RETURNVALUE_NOERROR then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, Game.getReturnMessage(returnValue)) + player:sendTextMessage(MESSAGE_INFO_DESCR, Game.getReturnMessage(returnValue)) else - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Raid started.") end return false diff --git a/data/talkactions/scripts/info.lua b/data/talkactions/scripts/info.lua index 8969b1cae8..f311bc6009 100644 --- a/data/talkactions/scripts/info.lua +++ b/data/talkactions/scripts/info.lua @@ -15,13 +15,13 @@ function onSay(player, words, param) end local targetIp = target:getIp() - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Name: " .. target:getName()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Level: " .. target:getLevel()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Magic Level: " .. target:getMagicLevel()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Speed: " .. target:getSpeed()) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "IP: " .. Game.convertIpToString(targetIp)) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Name: " .. target:getName()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Level: " .. target:getLevel()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Magic Level: " .. target:getMagicLevel()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Speed: " .. target:getSpeed()) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) + player:sendTextMessage(MESSAGE_INFO_DESCR, "IP: " .. Game.convertIpToString(targetIp)) local players = {} for _, targetPlayer in ipairs(Game.getPlayers()) do @@ -31,7 +31,7 @@ function onSay(player, words, param) end if #players > 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Other players on same IP: " .. table.concat(players, ", ") .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Other players on same IP: " .. table.concat(players, ", ") .. ".") end return false end diff --git a/data/talkactions/scripts/kills.lua b/data/talkactions/scripts/kills.lua index 360bf62d6e..d04df77d08 100644 --- a/data/talkactions/scripts/kills.lua +++ b/data/talkactions/scripts/kills.lua @@ -1,13 +1,13 @@ function onSay(player, words, param) local fragTime = configManager.getNumber(configKeys.FRAG_TIME) if fragTime <= 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You do not have any unjustified kill.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "You do not have any unjustified kill.") return false end local skullTime = player:getSkullTime() if skullTime <= 0 then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You do not have any unjustified kill.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "You do not have any unjustified kill.") return false end @@ -41,6 +41,6 @@ function onSay(player, words, param) message = message .. seconds .. " seconds." end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message) + player:sendTextMessage(MESSAGE_INFO_DESCR, message) return false end diff --git a/data/talkactions/scripts/looktype.lua b/data/talkactions/scripts/looktype.lua index 05d677d4ba..631c08fec9 100644 --- a/data/talkactions/scripts/looktype.lua +++ b/data/talkactions/scripts/looktype.lua @@ -14,7 +14,17 @@ local invalidTypes = { 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 847, 864, 865, 866, 867, 871, 872, 880, 891, 892, 893, - 894, 895, 896, 897, 898 + 894, 895, 896, 897, 898, 911, 912, 917, 930, 941, 942, 946, 953, 954, 983, + 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, + 1009, 1010, 1012, 1014, 1015, 1022, 1028, 1074, 1075, 1080, 1081, 1082, 1083, + 1084, 1085, 1086, 1087, 1089, 1090, 1096, 1097, 1098, 1099, 1100, 1141, 1145, + 1153, 1154, 1155, 1156, 1160, 1170, 1171, 1172, 1176, 1177, 1178, 1182, 1192, + 1193, 1194, 1198, 1215, 1216, 1225, 1226, 1227, 1228, 1235, 1236, 1237, 1238, + 1239, 1240, 1241, 1242, 1250, 1254, 1263, 1267, 1273, 1274, 1287, 1302, 1318, + 1319, 1320, 1327, 1328, 1329, 1330, 1340, 1343, 1345, 1347, 1348, 1349, 1350, + 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1368, + 1369, 1370, 1374, 1375, 1376, 1388, 1392, 1395, 1400, 1402, 1404, 1409, 1410, + 1411, 1420, 1421, 1427, 1429, 1432, 1433, 1434, 1435, 1438, 1442, 1443 } function onSay(player, words, param) @@ -23,7 +33,7 @@ function onSay(player, words, param) end local lookType = tonumber(param) - if lookType >= 0 and lookType < 903 and not table.contains(invalidTypes, lookType) then + if lookType >= 0 and lookType < 1450 and not table.contains(invalidTypes, lookType) then local playerOutfit = player:getOutfit() playerOutfit.lookType = lookType player:setOutfit(playerOutfit) diff --git a/data/talkactions/scripts/mccheck.lua b/data/talkactions/scripts/mccheck.lua index 13ffbc970e..6da986ced5 100644 --- a/data/talkactions/scripts/mccheck.lua +++ b/data/talkactions/scripts/mccheck.lua @@ -7,7 +7,7 @@ function onSay(player, words, param) return false end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Multiclient Check List:") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Multiclient Check List:") local ipList = {} local players = Game.getPlayers() @@ -33,7 +33,7 @@ function onSay(player, words, param) tmpPlayer = list[i] message = ("%s, %s [%d]"):format(message, tmpPlayer:getName(), tmpPlayer:getLevel()) end - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, message .. ".") end end return false diff --git a/data/talkactions/scripts/online.lua b/data/talkactions/scripts/online.lua index db7cceb325..09c79c9dbf 100644 --- a/data/talkactions/scripts/online.lua +++ b/data/talkactions/scripts/online.lua @@ -11,12 +11,12 @@ function onSay(player, words, param) end local playersOnline = #onlineList - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, ("%d players online."):format(playersOnline)) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, ("%d players online."):format(playersOnline)) for i = 1, playersOnline, maxPlayersPerMessage do local j = math.min(i + maxPlayersPerMessage - 1, playersOnline) local msg = table.concat(onlineList, ", ", i, j) .. "." - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, msg) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, msg) end return false end diff --git a/data/talkactions/scripts/openserver.lua b/data/talkactions/scripts/openserver.lua index c3896e7280..c9e0a27e86 100644 --- a/data/talkactions/scripts/openserver.lua +++ b/data/talkactions/scripts/openserver.lua @@ -8,6 +8,6 @@ function onSay(player, words, param) end Game.setGameState(GAME_STATE_NORMAL) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now open.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server is now open.") return false end diff --git a/data/talkactions/scripts/position.lua b/data/talkactions/scripts/position.lua index 299ce6e566..b5f373ecc9 100644 --- a/data/talkactions/scripts/position.lua +++ b/data/talkactions/scripts/position.lua @@ -4,7 +4,7 @@ function onSay(player, words, param) player:teleportTo(Position(split[1], split[2], split[3])) else local position = player:getPosition() - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") end return false end diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua index f11e9d51e8..6899f7c3ac 100644 --- a/data/talkactions/scripts/reload.lua +++ b/data/talkactions/scripts/reload.lua @@ -69,7 +69,7 @@ function onSay(player, words, param) local reloadType = reloadTypes[param:lower()] if not reloadType then - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Reload type not found.") return false end @@ -79,6 +79,6 @@ function onSay(player, words, param) end Game.reload(reloadType) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, string.format("Reloaded %s.", param:lower())) + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("Reloaded %s.", param:lower())) return false end diff --git a/data/talkactions/scripts/serverinfo.lua b/data/talkactions/scripts/serverinfo.lua index 6b26b9814d..fc6cc7a713 100644 --- a/data/talkactions/scripts/serverinfo.lua +++ b/data/talkactions/scripts/serverinfo.lua @@ -1,5 +1,5 @@ function onSay(player, words, param) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server Info:" + player:sendTextMessage(MESSAGE_INFO_DESCR, "Server Info:" .. "\nExp rate: " .. Game.getExperienceStage(player:getLevel()) .. "\nSkill rate: " .. configManager.getNumber(configKeys.RATE_SKILL) .. "\nMagic rate: " .. configManager.getNumber(configKeys.RATE_MAGIC) diff --git a/data/talkactions/scripts/uptime.lua b/data/talkactions/scripts/uptime.lua index 7c0e291ede..b77f76daac 100644 --- a/data/talkactions/scripts/uptime.lua +++ b/data/talkactions/scripts/uptime.lua @@ -3,6 +3,6 @@ function onSay(player, words, param) local hours = math.floor(uptime / 3600) local minutes = math.floor((uptime - (3600 * hours)) / 60) - player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") return false end diff --git a/schema.sql b/schema.sql index 9605da961f..4ff4cd3571 100644 --- a/schema.sql +++ b/schema.sql @@ -27,6 +27,11 @@ CREATE TABLE IF NOT EXISTS `players` ( `looklegs` int NOT NULL DEFAULT '0', `looktype` int NOT NULL DEFAULT '136', `lookaddons` int NOT NULL DEFAULT '0', + `lookmount` int NOT NULL DEFAULT '0', + `lookmounthead` int NOT NULL DEFAULT '0', + `lookmountbody` int NOT NULL DEFAULT '0', + `lookmountlegs` int NOT NULL DEFAULT '0', + `lookmountfeet` int NOT NULL DEFAULT '0', `direction` tinyint unsigned NOT NULL DEFAULT '2', `maglevel` int NOT NULL DEFAULT '0', `mana` int NOT NULL DEFAULT '0', @@ -357,7 +362,7 @@ CREATE TABLE IF NOT EXISTS `towns` ( UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8; -INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '29'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); +INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '30'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0'); DROP TRIGGER IF EXISTS `ondelete_players`; DROP TRIGGER IF EXISTS `oncreate_guilds`; diff --git a/src/connection.cpp b/src/connection.cpp index a2d294e14f..6cfbfc0419 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -67,14 +67,10 @@ void Connection::close(bool force) ConnectionManager::getInstance().releaseConnection(shared_from_this()); std::lock_guard lockClass(connectionLock); - if (closed) { - return; - } - closed = true; + connectionState = CONNECTION_STATE_DISCONNECTED; if (protocol) { - g_dispatcher.addTask( - createTask(std::bind(&Protocol::release, protocol))); + g_dispatcher.addTask(createTask(std::bind(&Protocol::release, protocol))); } if (messageQueue.empty() || force) { @@ -108,21 +104,26 @@ void Connection::accept(Protocol_ptr protocol) { this->protocol = protocol; g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol))); - + connectionState = CONNECTION_STATE_GAMEWORLD_AUTH; accept(); } void Connection::accept() { + if (connectionState == CONNECTION_STATE_PENDING) { + connectionState = CONNECTION_STATE_REQUEST_CHARLIST; + } + std::lock_guard lockClass(connectionLock); try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); // Read size of the first packet + auto bufferLength = !receivedLastChar && receivedName && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH ? 1 : NetworkMessage::HEADER_LENGTH; boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + boost::asio::buffer(msg.getBuffer(), bufferLength), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::accept] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -137,7 +138,7 @@ void Connection::parseHeader(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } @@ -148,6 +149,32 @@ void Connection::parseHeader(const boost::system::error_code& error) return; } + if (!receivedLastChar && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH) { + uint8_t* msgBuffer = msg.getBuffer(); + + if (!receivedName && msgBuffer[1] == 0x00) { + receivedLastChar = true; + } else { + if (!receivedName) { + receivedName = true; + + accept(); + return; + } + + if (msgBuffer[0] == 0x0A) { + receivedLastChar = true; + } + + accept(); + return; + } + } + + if (receivedLastChar && connectionState == CONNECTION_STATE_GAMEWORLD_AUTH) { + connectionState = CONNECTION_STATE_GAME; + } + if (timePassed > 2) { timeConnected = time(nullptr); packetsSent = 0; @@ -162,12 +189,12 @@ void Connection::parseHeader(const boost::system::error_code& error) try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + std::placeholders::_1)); // Read packet content msg.setLength(size + NetworkMessage::HEADER_LENGTH); boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size), - std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); + std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -182,32 +209,25 @@ void Connection::parsePacket(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } - //Check packet checksum - uint32_t checksum; - int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH; - if (len > 0) { - checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len); - } else { - checksum = 0; - } - - uint32_t recvChecksum = msg.get(); - if (recvChecksum != checksum) { - // it might not have been the checksum, step back - msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); - } + // Read potential checksum bytes + msg.get(); if (!receivedFirst) { - // First message received receivedFirst = true; if (!protocol) { + // Skip deprecated checksum bytes (with clients that aren't using it in mind) + uint16_t len = msg.getLength(); + if (len < 280 && len != 151) { + msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); + } + // Game protocol has already been created at this point - protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this()); + protocol = service_port->make_protocol(msg, shared_from_this()); if (!protocol) { close(FORCE_CLOSE); return; @@ -224,12 +244,12 @@ void Connection::parsePacket(const boost::system::error_code& error) try { readTimer.expires_from_now(std::chrono::seconds(CONNECTION_READ_TIMEOUT)); readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + std::placeholders::_1)); // Wait to the next packet boost::asio::async_read(socket, - boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), - std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -239,7 +259,7 @@ void Connection::parsePacket(const boost::system::error_code& error) void Connection::send(const OutputMessage_ptr& msg) { std::lock_guard lockClass(connectionLock); - if (closed) { + if (connectionState == CONNECTION_STATE_DISCONNECTED) { return; } @@ -256,11 +276,11 @@ void Connection::internalSend(const OutputMessage_ptr& msg) try { writeTimer.expires_from_now(std::chrono::seconds(CONNECTION_WRITE_TIMEOUT)); writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), - std::placeholders::_1)); + std::placeholders::_1)); boost::asio::async_write(socket, - boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), - std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), + std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); } catch (boost::system::system_error& e) { std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl; close(FORCE_CLOSE); @@ -295,7 +315,7 @@ void Connection::onWriteOperation(const boost::system::error_code& error) if (!messageQueue.empty()) { internalSend(messageQueue.front()); - } else if (closed) { + } else if (connectionState == CONNECTION_STATE_DISCONNECTED) { closeSocket(); } } @@ -303,7 +323,7 @@ void Connection::onWriteOperation(const boost::system::error_code& error) void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error) { if (error == boost::asio::error::operation_aborted) { - //The timer has been manually canceled + // The timer has been cancelled manually return; } diff --git a/src/connection.h b/src/connection.h index 6b847cfaf9..44ede515e6 100644 --- a/src/connection.h +++ b/src/connection.h @@ -24,6 +24,20 @@ #include "networkmessage.h" +enum ConnectionState_t { + CONNECTION_STATE_DISCONNECTED, + CONNECTION_STATE_REQUEST_CHARLIST, + CONNECTION_STATE_GAMEWORLD_AUTH, + CONNECTION_STATE_GAME, + CONNECTION_STATE_PENDING +}; + +enum checksumMode_t { + CHECKSUM_DISABLED, + CHECKSUM_ADLER, + CHECKSUM_SEQUENCE +}; + static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; @@ -121,8 +135,10 @@ class Connection : public std::enable_shared_from_this time_t timeConnected; uint32_t packetsSent = 0; - bool closed = false; + ConnectionState_t connectionState = CONNECTION_STATE_PENDING; bool receivedFirst = false; + bool receivedName = false; + bool receivedLastChar = false; }; #endif diff --git a/src/const.h b/src/const.h index 7a6836b99e..61e3076ef3 100644 --- a/src/const.h +++ b/src/const.h @@ -22,6 +22,15 @@ static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590; +enum MagicEffectsType_t : uint8_t { + MAGIC_EFFECTS_END_LOOP = 0, // ends the magic effect loop + MAGIC_EFFECTS_DELTA = 1, // needs uint8_t delta after type to adjust position + MAGIC_EFFECTS_DELAY = 2, // needs uint16_t delay after type to delay in miliseconds effect display + MAGIC_EFFECTS_CREATE_EFFECT = 3, // needs uint8_t effectid after type + MAGIC_EFFECTS_CREATE_DISTANCEEFFECT = 4, // needs uint8_t and deltaX(int8_t), deltaY(int8_t) after type + MAGIC_EFFECTS_CREATE_DISTANCEEFFECT_REVERSED = 5, // needs uint8_t and deltaX(int8_t), deltaY(int8_t) after type +}; + enum MagicEffectClasses : uint8_t { CONST_ME_NONE, @@ -113,6 +122,57 @@ enum MagicEffectClasses : uint8_t { CONST_ME_CRITICAL_DAMAGE = 173, // 174 is empty CONST_ME_PLUNGING_FISH = 175, + CONST_ME_BLUECHAIN = 176, + CONST_ME_ORANGECHAIN = 177, + CONST_ME_GREENCHAIN = 178, + CONST_ME_PURPLECHAIN = 179, + CONST_ME_GREYCHAIN = 180, + CONST_ME_YELLOWCHAIN = 181, + CONST_ME_YELLOWSPARKLES = 182, + // 183 is empty + CONST_ME_FAEEXPLOSION = 184, + CONST_ME_FAECOMING = 185, + CONST_ME_FAEGOING = 186, + // 187 is empty + CONST_ME_BIGCLOUDSSINGLESPACE = 188, + CONST_ME_STONESSINGLESPACE = 189, + // 190 is empty + CONST_ME_BLUEGHOST = 191, + // 192 is empty + CONST_ME_POINTOFINTEREST = 193, + CONST_ME_MAPEFFECT = 194, + CONST_ME_PINKSPARK = 195, + CONST_ME_FIREWORK_GREEN = 196, + CONST_ME_FIREWORK_ORANGE = 197, + CONST_ME_FIREWORK_PURPLE = 198, + CONST_ME_FIREWORK_TURQUOISE = 199, + // 200 us empty + CONST_ME_THECUBE = 201, + CONST_ME_DRAWINK = 202, + CONST_ME_PRISMATICSPARKLES = 203, + CONST_ME_THAIAN = 204, + CONST_ME_THAIANGHOST = 205, + CONST_ME_GHOSTSMOKE = 206, + // 207 is empty + CONST_ME_FLOATINGBLOCK = 208, + CONST_ME_BLOCK = 209, + CONST_ME_ROOTING = 210, + CONST_ME_SUNPRIEST = 211, + CONST_ME_WERELION = 212, + CONST_ME_GHOSTLYSCRATCH = 213, + CONST_ME_GHOSTLYBITE = 214, + CONST_ME_BIGSCRATCHING = 215, + CONST_ME_SLASH = 216, + CONST_ME_BITE = 217, + // 218 is empty + CONST_ME_CHIVALRIOUSCHALLENGE = 219, + CONST_ME_DIVINEDAZZLE = 220, + CONST_ME_ELECTRICALSPARK = 221, + CONST_ME_PURPLETELEPORT = 222, + CONST_ME_REDTELEPORT = 223, + CONST_ME_ORANGETELEPORT = 224, + CONST_ME_GREYTELEPORT = 225, + CONST_ME_LIGHTBLUETELEPORT = 226, }; enum ShootType_t : uint8_t { @@ -172,6 +232,11 @@ enum ShootType_t : uint8_t { CONST_ANI_GLOOTHSPEAR = 53, CONST_ANI_SIMPLEARROW = 54, + CONST_ANI_LEAFSTAR = 56, + CONST_ANI_DIAMONDARROW = 57, + CONST_ANI_SPECTRALBOLT = 58, + CONST_ANI_ROYALSTAR = 59, + // for internal use, don't send to client CONST_ANI_WEAPONTYPE = 0xFE, // 254 }; @@ -180,31 +245,33 @@ enum SpeakClasses : uint8_t { TALKTYPE_SAY = 1, TALKTYPE_WHISPER = 2, TALKTYPE_YELL = 3, - TALKTYPE_PRIVATE_FROM = 4, - TALKTYPE_PRIVATE_TO = 5, + TALKTYPE_PRIVATE_FROM = 4, // Received private message + TALKTYPE_PRIVATE_TO = 5, // Sent private message + //TALKTYPE_CHANNEL_M = 6 // not working (?) TALKTYPE_CHANNEL_Y = 7, TALKTYPE_CHANNEL_O = 8, - TALKTYPE_PRIVATE_NP = 10, - TALKTYPE_PRIVATE_PN = 12, + TALKTYPE_SPELL = 9, // Like SAY but with "casts" instead of "says" + TALKTYPE_PRIVATE_NP = 10, // NPC speaking to player + TALKTYPE_PRIVATE_NP_CONSOLE = 11, // NPC channel message, no text on game screen, for sendPrivateMessage use only + TALKTYPE_PRIVATE_PN = 12, // Player speaking to NPC TALKTYPE_BROADCAST = 13, - TALKTYPE_CHANNEL_R1 = 14, //red - #c text - TALKTYPE_PRIVATE_RED_FROM = 15, //@name@text - TALKTYPE_PRIVATE_RED_TO = 16, //@name@text + TALKTYPE_CHANNEL_R1 = 14, // red - #c text + TALKTYPE_PRIVATE_RED_FROM = 15, // @name@text + TALKTYPE_PRIVATE_RED_TO = 16, // @name@text TALKTYPE_MONSTER_SAY = 36, TALKTYPE_MONSTER_YELL = 37, + TALKTYPE_POTION = 52, // Like MONSTER_SAY but can be disabled in client settings }; enum MessageClasses : uint8_t { - MESSAGE_STATUS_CONSOLE_BLUE = 4, /*FIXME Blue message in the console*/ - - MESSAGE_STATUS_CONSOLE_RED = 13, /*Red message in the console*/ - - MESSAGE_STATUS_DEFAULT = 17, /*White message at the bottom of the game window and in the console*/ - MESSAGE_STATUS_WARNING = 18, /*Red message in game window and in the console*/ - MESSAGE_EVENT_ADVANCE = 19, /*White message in game window and in the console*/ - - MESSAGE_STATUS_SMALL = 21, /*White message at the bottom of the game window"*/ - MESSAGE_INFO_DESCR = 22, /*Green message in game window and in the console*/ + MESSAGE_STATUS_DEFAULT = 17, // White, bottom + console + MESSAGE_STATUS_WARNING = 18, // Red, over player + console + MESSAGE_EVENT_ADVANCE = 19, // White, over player + console + MESSAGE_STATUS_WARNING2 = 20, // Red, over player + console + MESSAGE_STATUS_SMALL = 21, // White, bottom of the screen + MESSAGE_INFO_DESCR = 22, // Green, over player + console + + // White, console MESSAGE_DAMAGE_DEALT = 23, MESSAGE_DAMAGE_RECEIVED = 24, MESSAGE_HEALED = 25, @@ -212,14 +279,30 @@ enum MessageClasses : uint8_t { MESSAGE_DAMAGE_OTHERS = 27, MESSAGE_HEALED_OTHERS = 28, MESSAGE_EXPERIENCE_OTHERS = 29, - MESSAGE_EVENT_DEFAULT = 30, /*White message at the bottom of the game window and in the console*/ - MESSAGE_LOOT = 31, - - MESSAGE_GUILD = 33, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY_MANAGEMENT = 34, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY = 35, /*White message in channel (+ channelId)*/ - MESSAGE_EVENT_ORANGE = 36, /*Orange message in the console*/ - MESSAGE_STATUS_CONSOLE_ORANGE = 37, /*Orange message in the console*/ + + MESSAGE_EVENT_DEFAULT = 30, // White, bottom + console + MESSAGE_LOOT = 31, // White, over player + console, supports colors as {text|itemClientId} + MESSAGE_TRADE = 32, // Green, over player + console + + // White, in channel (needs channel Id) + MESSAGE_GUILD = 33, + MESSAGE_PARTY_MANAGEMENT = 34, + MESSAGE_PARTY = 35, + + MESSAGE_REPORT = 38, // White, over player + conosle + MESSAGE_HOTKEY_PRESSED = 39, // Green, over player + console + //MESSAGE_TUTORIAL_HINT = 40, // not working (?) + //MESSAGE_THANK_YOU = 41, // not working (?) + MESSAGE_MARKET = 42, // Window "Market Message" + "Ok" button + //MESSAGE_MANA = 43, // not working (?) + MESSAGE_BEYOND_LAST = 44, // White, console only + MESSAGE_TOURNAMENT_INFO = 45, // Window "Tournament" + "Ok" button + // unused 46? + // unused 47? + MESSAGE_ATTENTION = 48, // White, console only + MESSAGE_BOOSTED_CREATURE = 49, // White, console only + MESSAGE_OFFLINE_TRAINING = 50, // White, over player + console + MESSAGE_TRANSACTION = 51, // White, console only }; enum FluidColors_t : uint8_t { @@ -358,6 +441,17 @@ enum Icons_t { ICON_REDSWORDS = 1 << 13, ICON_PIGEON = 1 << 14, ICON_BLEEDING = 1 << 15, + ICON_LESSERHEX = 1 << 16, + ICON_INTENSEHEX = 1 << 17, + ICON_GREATERHEX = 1 << 18, + ICON_ROOT = 1 << 19, + ICON_FEAR = 1 << 20, + ICON_GOSHNAR1 = 1 << 21, + ICON_GOSHNAR2 = 1 << 22, + ICON_GOSHNAR3 = 1 << 23, + ICON_GOSHNAR4 = 1 << 24, + ICON_GOSHNAR5 = 1 << 25, + ICON_MANASHIELD_BREAKABLE = 1 << 26, }; enum WeaponType_t : uint8_t { @@ -507,6 +601,14 @@ enum item_t : uint16_t { ITEM_DOCUMENT_RO = 1968, //read-only }; +enum ResourceTypes_t: uint8_t { + RESOURCE_BANK_BALANCE = 0x00, + RESOURCE_GOLD_EQUIPPED = 0x01, + RESOURCE_PREY_WILDCARDS = 0x0A, + RESOURCE_DAILYREWARD_STREAK = 0x14, + RESOURCE_DAILYREWARD_JOKERS = 0x15, +}; + enum PlayerFlags : uint64_t { PlayerFlag_CannotUseCombat = 1 << 0, PlayerFlag_CannotAttackPlayer = 1 << 1, diff --git a/src/creature.h b/src/creature.h index ac4b4266be..eab9b949e9 100644 --- a/src/creature.h +++ b/src/creature.h @@ -71,6 +71,8 @@ class Tile; static constexpr int32_t EVENT_CREATURECOUNT = 10; static constexpr int32_t EVENT_CREATURE_THINK_INTERVAL = 1000; static constexpr int32_t EVENT_CHECK_CREATURE_INTERVAL = (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT); +static constexpr uint32_t CREATURE_ID_MIN = 0x10000000; +static constexpr uint32_t CREATURE_ID_MAX = std::numeric_limits::max(); class FrozenPathingConditionCall { diff --git a/src/definitions.h b/src/definitions.h index 274a1eb84a..5c3f9b70be 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -24,9 +24,9 @@ static constexpr auto STATUS_SERVER_NAME = "The Forgotten Server"; static constexpr auto STATUS_SERVER_VERSION = "1.4"; static constexpr auto STATUS_SERVER_DEVELOPERS = "Mark Samman"; -static constexpr auto CLIENT_VERSION_MIN = 1097; -static constexpr auto CLIENT_VERSION_MAX = 1098; -static constexpr auto CLIENT_VERSION_STR = "10.98"; +static constexpr auto CLIENT_VERSION_MIN = 1270; +static constexpr auto CLIENT_VERSION_MAX = 1272; +static constexpr auto CLIENT_VERSION_STR = "12.71"; static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; diff --git a/src/enums.h b/src/enums.h index 21b7fd9d3d..a9498ebcc9 100644 --- a/src/enums.h +++ b/src/enums.h @@ -93,6 +93,7 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_WRAPID = 1 << 24, ITEM_ATTRIBUTE_STOREITEM = 1 << 25, ITEM_ATTRIBUTE_ATTACK_SPEED = 1 << 26, + ITEM_ATTRIBUTE_OPENCONTAINER = 1 << 27, ITEM_ATTRIBUTE_CUSTOM = 1U << 31 }; @@ -135,6 +136,7 @@ enum CreatureType_t : uint8_t { CREATURETYPE_NPC = 2, CREATURETYPE_SUMMON_OWN = 3, CREATURETYPE_SUMMON_OTHERS = 4, + CREATURETYPE_HIDDEN = 5, }; enum OperatingSystem_t : uint8_t { @@ -143,6 +145,9 @@ enum OperatingSystem_t : uint8_t { CLIENTOS_LINUX = 1, CLIENTOS_WINDOWS = 2, CLIENTOS_FLASH = 3, + CLIENTOS_QT_LINUX = 4, + CLIENTOS_QT_WINDOWS = 5, + CLIENTOS_QT_MAC = 6, CLIENTOS_OTCLIENT_LINUX = 10, CLIENTOS_OTCLIENT_WINDOWS = 11, @@ -155,6 +160,10 @@ enum SpellGroup_t : uint8_t { SPELLGROUP_HEALING = 2, SPELLGROUP_SUPPORT = 3, SPELLGROUP_SPECIAL = 4, + //SPELLGROUP_CONJURE = 5, + SPELLGROUP_CRIPPLING = 6, + SPELLGROUP_FOCUS = 7, + SPELLGROUP_ULTIMATESTRIKES = 8, }; enum SpellType_t : uint8_t { @@ -473,7 +482,10 @@ enum SpeechBubble_t SPEECHBUBBLE_NORMAL = 1, SPEECHBUBBLE_TRADE = 2, SPEECHBUBBLE_QUEST = 3, - SPEECHBUBBLE_QUESTTRADER = 4, + SPEECHBUBBLE_COMPASS = 4, + SPEECHBUBBLE_NORMAL2 = 5, + SPEECHBUBBLE_NORMAL3 = 6, + SPEECHBUBBLE_HIRELING = 7, }; enum MapMark_t @@ -503,12 +515,16 @@ enum MapMark_t struct Outfit_t { uint16_t lookType = 0; uint16_t lookTypeEx = 0; - uint16_t lookMount = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; uint8_t lookFeet = 0; uint8_t lookAddons = 0; + uint16_t lookMount = 0; + uint8_t lookMountHead = 0; + uint8_t lookMountBody = 0; + uint8_t lookMountLegs = 0; + uint8_t lookMountFeet = 0; }; struct LightInfo { diff --git a/src/game.cpp b/src/game.cpp index 1584b64732..b33dceefff 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -374,12 +374,12 @@ void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) Creature* Game::getCreatureByID(uint32_t id) { - if (id <= Player::playerAutoID) { + if (id <= Player::playerIDLimit) { return getPlayerByID(id); - } else if (id <= Monster::monsterAutoID) { - return getMonsterByID(id); } else if (id <= Npc::npcAutoID) { return getNpcByID(id); + } else if (id <= Monster::monsterAutoID) { + return getMonsterByID(id); } return nullptr; } @@ -2804,7 +2804,7 @@ void Game::playerAcceptTrade(uint32_t playerId) if (player->getInventoryItem(CONST_SLOT_BACKPACK) == playerTradeItem && tradePartner->getInventoryItem(CONST_SLOT_BACKPACK) == partnerTradeItem) { playerRet = RETURNVALUE_NOTENOUGHROOM; } - + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); @@ -3357,17 +3357,16 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) return; } + int32_t speedChange = mount->speed; if (player->isMounted()) { Mount* prevMount = mounts.getMountByID(player->getCurrentMount()); if (prevMount) { - changeSpeed(player, mount->speed - prevMount->speed); + speedChange -= prevMount->speed; } - - player->setCurrentMount(mount->id); - } else { - player->setCurrentMount(mount->id); - outfit.lookMount = 0; } + + changeSpeed(player, speedChange); + player->setCurrentMount(mount->id); } else if (player->isMounted()) { player->dismount(); } @@ -3484,7 +3483,7 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& result = g_spells->playerSaySpell(player, words); if (result == TALKACTION_BREAK) { if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) { - return internalCreatureSay(player, TALKTYPE_SAY, words, false); + return internalCreatureSay(player, TALKTYPE_SPELL, words, false); } else { return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); } @@ -4716,54 +4715,6 @@ void Game::updatePlayerShield(Player* player) } } -void Game::updatePlayerHelpers(const Player& player) -{ - uint32_t creatureId = player.getID(); - uint16_t helpers = player.getHelpers(); - - SpectatorVec spectators; - map.getSpectators(spectators, player.getPosition(), true, true); - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers); - } -} - -void Game::updateCreatureType(Creature* creature) -{ - const Player* masterPlayer = nullptr; - - uint32_t creatureId = creature->getID(); - CreatureType_t creatureType = creature->getType(); - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - masterPlayer = master->getPlayer(); - if (masterPlayer) { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } - } - - //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - - if (creatureType == CREATURETYPE_SUMMON_OTHERS) { - for (Creature* spectator : spectators) { - Player* player = spectator->getPlayer(); - if (masterPlayer == player) { - player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); - } else { - player->sendCreatureType(creatureId, creatureType); - } - } - } else { - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureType(creatureId, creatureType); - } - } -} - void Game::loadMotdNum() { Database& db = Database::getInstance(); diff --git a/src/game.h b/src/game.h index 8c11db2197..7799dab880 100644 --- a/src/game.h +++ b/src/game.h @@ -437,8 +437,6 @@ class Game void changeLight(const Creature* creature); void updateCreatureSkull(const Creature* creature); void updatePlayerShield(Player* player); - void updatePlayerHelpers(const Player& player); - void updateCreatureType(Creature* creature); void updateCreatureWalkthrough(const Creature* creature); GameState_t getGameState() const; diff --git a/src/guild.cpp b/src/guild.cpp index dd211103bd..1efddd4b97 100644 --- a/src/guild.cpp +++ b/src/guild.cpp @@ -28,18 +28,11 @@ extern Game g_game; void Guild::addMember(Player* player) { membersOnline.push_back(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } } void Guild::removeMember(Player* player) { membersOnline.remove(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*player); if (membersOnline.empty()) { g_game.removeGuild(id); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 0cabd76009..68d0e6e8f7 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -77,7 +77,7 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: { Database& db = Database::getInstance(); - DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id`, `name`, `password`, `secret`, `type`, `premium_ends_at` FROM `accounts` WHERE `name` = {:s}", db.escapeString(name))); + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id`, `name`, `password`, `secret`, `type`, `premium_ends_at` FROM `accounts` WHERE `name` = {:s} OR `email` = {:s}", db.escapeString(name), db.escapeString(name))); if (!result) { return false; } @@ -104,8 +104,7 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime) { Database& db = Database::getInstance(); - - DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = {:s}", db.escapeString(accountName))); + DBResult_ptr result = db.storeQuery(fmt::format("SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = {:s} OR `email` = {:s}", db.escapeString(accountName), db.escapeString(accountName))); if (!result) { return 0; } @@ -211,13 +210,13 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name) bool IOLoginData::loadPlayerById(Player* player, uint32_t id) { Database& db = Database::getInstance(); - return loadPlayer(player, db.storeQuery(fmt::format("SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", id))); + return loadPlayer(player, db.storeQuery(fmt::format("SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `id` = {:d}", id))); } bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) { Database& db = Database::getInstance(); - return loadPlayer(player, db.storeQuery(fmt::format("SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", db.escapeString(name)))); + return loadPlayer(player, db.storeQuery(fmt::format("SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `lookaddons`, `lookmount`, `lookmounthead`, `lookmountbody`, `lookmountlegs`, `lookmountfeet`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `direction` FROM `players` WHERE `name` = {:s}", db.escapeString(name)))); } bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) @@ -313,6 +312,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->defaultOutfit.lookLegs = result->getNumber("looklegs"); player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); + player->defaultOutfit.lookMount = result->getNumber("lookmount"); + player->defaultOutfit.lookMountHead = result->getNumber("lookmounthead"); + player->defaultOutfit.lookMountBody = result->getNumber("lookmountbody"); + player->defaultOutfit.lookMountLegs = result->getNumber("lookmountlegs"); + player->defaultOutfit.lookMountFeet = result->getNumber("lookmountfeet"); player->currentOutfit = player->defaultOutfit; player->direction = static_cast (result->getNumber("direction")); @@ -419,6 +423,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) //load inventory items ItemMap itemMap; + std::map openContainersList; if ((result = db.storeQuery(fmt::format("SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = {:d} ORDER BY `sid` DESC", player->getGUID())))) { loadItems(itemMap, result); @@ -427,6 +432,15 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) const std::pair& pair = it->second; Item* item = pair.first; int32_t pid = pair.second; + + Container* itemContainer = item->getContainer(); + if (itemContainer) { + uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace(cid, itemContainer); + } + } + if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { player->internalAddThing(pid, item); } else { @@ -443,6 +457,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } + for (auto& it : openContainersList) { + player->addContainer(it.first - 1, it.second); + player->onSendContainer(it.second); + } + //load depot items itemMap.clear(); @@ -556,6 +575,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, containers.reserve(32); int32_t runningId = 100; + const auto& openContainers = player->getOpenContainers(); Database& db = Database::getInstance(); for (const auto& it : itemList) { @@ -563,6 +583,26 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, Item* item = it.second; ++runningId; + if (Container* container = item->getContainer()) { + if (container->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& its : openContainers) { + auto openContainer = its.second; + auto opcontainer = openContainer.container; + + if (opcontainer == container) { + container->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, static_cast(its.first) + 1); + break; + } + } + } + + containers.emplace_back(container, runningId); + } + propWriteStream.clear(); item->serializeAttr(propWriteStream); @@ -572,10 +612,6 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, if (!query_insert.addRow(fmt::format("{:d}, {:d}, {:d}, {:d}, {:d}, {:s}", player->getGUID(), pid, runningId, item->getID(), item->getSubType(), db.escapeBlob(attributes, attributesSize)))) { return false; } - - if (Container* container = item->getContainer()) { - containers.emplace_back(container, runningId); - } } for (size_t i = 0; i < containers.size(); i++) { @@ -589,6 +625,22 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, Container* subContainer = item->getContainer(); if (subContainer) { containers.emplace_back(subContainer, runningId); + + if (subContainer->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, 0); + } + + if (!openContainers.empty()) { + for (const auto& it : openContainers) { + auto openContainer = it.second; + auto opcontainer = openContainer.container; + + if (opcontainer == subContainer) { + subContainer->setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, it.first + 1); + break; + } + } + } } propWriteStream.clear(); @@ -649,6 +701,11 @@ bool IOLoginData::savePlayer(Player* player) query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ','; query << "`looktype` = " << player->defaultOutfit.lookType << ','; query << "`lookaddons` = " << static_cast(player->defaultOutfit.lookAddons) << ','; + query << "`lookmount` = " << player->defaultOutfit.lookMount << ','; + query << "`lookmounthead` = " << static_cast(player->defaultOutfit.lookMountHead) << ','; + query << "`lookmountbody` = " << static_cast(player->defaultOutfit.lookMountBody) << ','; + query << "`lookmountlegs` = " << static_cast(player->defaultOutfit.lookMountLegs) << ','; + query << "`lookmountfeet` = " << static_cast(player->defaultOutfit.lookMountFeet) << ','; query << "`maglevel` = " << player->magLevel << ','; query << "`mana` = " << player->mana << ','; query << "`manamax` = " << player->manaMax << ','; diff --git a/src/item.cpp b/src/item.cpp index 72f7290dcb..f768c125ea 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -616,6 +616,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_OPENCONTAINER: { + uint8_t openContainer; + if (!propStream.read(openContainer)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER, openContainer); + break; + } + //these should be handled through derived classes //If these are called then something has changed in the items.xml since the map was saved //just read the values @@ -841,6 +851,11 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_STOREITEM)); } + if (hasAttribute(ITEM_ATTRIBUTE_OPENCONTAINER)) { + propWriteStream.write(ATTR_OPENCONTAINER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER)); + } + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap(); propWriteStream.write(ATTR_CUSTOM_ATTRIBUTES); diff --git a/src/item.h b/src/item.h index 5a4c6896ab..0ffb61f281 100644 --- a/src/item.h +++ b/src/item.h @@ -106,6 +106,7 @@ enum AttrTypes_t { ATTR_WRAPID = 36, ATTR_STOREITEM = 37, ATTR_ATTACK_SPEED = 38, + ATTR_OPENCONTAINER = 39, }; enum Attr_ReadValue { @@ -503,7 +504,7 @@ class ItemAttributes | ITEM_ATTRIBUTE_ARMOR | ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER | ITEM_ATTRIBUTE_DURATION | ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES | ITEM_ATTRIBUTE_FLUIDTYPE | ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_DECAYTO | ITEM_ATTRIBUTE_WRAPID | ITEM_ATTRIBUTE_STOREITEM - | ITEM_ATTRIBUTE_ATTACK_SPEED; + | ITEM_ATTRIBUTE_ATTACK_SPEED | ITEM_ATTRIBUTE_OPENCONTAINER; const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME; diff --git a/src/itemloader.h b/src/itemloader.h index 159ee2a970..5858756b67 100644 --- a/src/itemloader.h +++ b/src/itemloader.h @@ -39,6 +39,7 @@ enum itemgroup_t { ITEM_GROUP_FLUID, ITEM_GROUP_DOOR, //deprecated ITEM_GROUP_DEPRECATED, + ITEM_GROUP_PODIUM, ITEM_GROUP_LAST }; @@ -103,6 +104,10 @@ enum clientVersion_t { CLIENT_VERSION_1035 = 55, CLIENT_VERSION_1076 = 56, CLIENT_VERSION_1098 = 57, + CLIENT_VERSION_1100 = 58, + CLIENT_VERSION_1271 = 59, + + CLIENT_VERSION_LAST = CLIENT_VERSION_1271 }; enum rootattrib_ { @@ -176,6 +181,8 @@ enum itemflags_t { FLAG_ANIMATION = 1 << 24, FLAG_FULLTILE = 1 << 25, // unused FLAG_FORCEUSE = 1 << 26, + FLAG_AMMO = 1 << 27, // unused + FLAG_REPORTABLE = 1 << 28, // unused }; //1-byte aligned structs diff --git a/src/items.cpp b/src/items.cpp index d310e8c831..e51bc91d4b 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -217,8 +217,8 @@ const std::unordered_map FluidTypesMap = { Items::Items() { - items.reserve(30000); - nameToItems.reserve(30000); + items.reserve(45000); + nameToItems.reserve(45000); } void Items::clear() @@ -293,7 +293,7 @@ bool Items::loadFromOtb(const std::string& file) } else if (majorVersion != 3) { std::cout << "Old version detected, a newer version of items.otb is required." << std::endl; return false; - } else if (minorVersion < CLIENT_VERSION_1098) { + } else if (minorVersion < CLIENT_VERSION_LAST) { std::cout << "A newer version of items.otb is required." << std::endl; return false; } @@ -436,6 +436,7 @@ bool Items::loadFromOtb(const std::string& file) case ITEM_GROUP_FLUID: case ITEM_GROUP_CHARGES: case ITEM_GROUP_DEPRECATED: + case ITEM_GROUP_PODIUM: break; default: return false; @@ -510,32 +511,9 @@ bool Items::loadFromXml() } } - buildInventoryList(); return true; } -void Items::buildInventoryList() -{ - inventory.reserve(items.size()); - for (const auto& type: items) { - if (type.weaponType != WEAPON_NONE || type.ammoType != AMMO_NONE || - type.attack != 0 || type.defense != 0 || - type.extraDefense != 0 || type.armor != 0 || - type.slotPosition & SLOTP_NECKLACE || - type.slotPosition & SLOTP_RING || - type.slotPosition & SLOTP_AMMO || - type.slotPosition & SLOTP_FEET || - type.slotPosition & SLOTP_HEAD || - type.slotPosition & SLOTP_ARMOR || - type.slotPosition & SLOTP_LEGS) - { - inventory.push_back(type.clientId); - } - } - inventory.shrink_to_fit(); - std::sort(inventory.begin(), inventory.end()); -} - void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) { if (id > 0 && id < 100) { diff --git a/src/items.h b/src/items.h index 5640b1fd99..d7c1858187 100644 --- a/src/items.h +++ b/src/items.h @@ -423,11 +423,6 @@ class Items bool loadFromXml(); void parseItemNode(const pugi::xml_node& itemNode, uint16_t id); - void buildInventoryList(); - const InventoryVector& getInventory() const { - return inventory; - } - size_t size() const { return items.size(); } @@ -441,7 +436,7 @@ class Items { public: ClientIdToServerIdMap() { - vec.reserve(30000); + vec.reserve(45000); } void emplace(uint16_t clientId, uint16_t serverId) { diff --git a/src/luascript.cpp b/src/luascript.cpp index 9a1955ccd1..1dc9a61c27 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -154,7 +154,7 @@ void ScriptEnvironment::insertItem(uint32_t uid, Item* item) Thing* ScriptEnvironment::getThingByUID(uint32_t uid) { - if (uid >= 0x10000000) { + if (uid >= CREATURE_ID_MIN) { return g_game.getCreatureByID(uid); } @@ -773,18 +773,23 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) { Outfit_t outfit; - outfit.lookMount = getField(L, arg, "lookMount"); - outfit.lookAddons = getField(L, arg, "lookAddons"); - outfit.lookFeet = getField(L, arg, "lookFeet"); - outfit.lookLegs = getField(L, arg, "lookLegs"); - outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookType = getField(L, arg, "lookType"); + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookHead = getField(L, arg, "lookHead"); + outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookLegs = getField(L, arg, "lookLegs"); + outfit.lookFeet = getField(L, arg, "lookFeet"); + outfit.lookAddons = getField(L, arg, "lookAddons"); - outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); - outfit.lookType = getField(L, arg, "lookType"); + outfit.lookMount = getField(L, arg, "lookMount"); + outfit.lookMountHead = getField(L, arg, "lookMountHead"); + outfit.lookMountBody = getField(L, arg, "lookMountBody"); + outfit.lookMountLegs = getField(L, arg, "lookMountLegs"); + outfit.lookMountFeet = getField(L, arg, "lookMountFeet"); - lua_pop(L, 8); + lua_pop(L, 12); return outfit; } @@ -952,7 +957,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) { - lua_createtable(L, 0, 8); + lua_createtable(L, 0, 12); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookHead", outfit.lookHead); @@ -961,6 +966,10 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookFeet", outfit.lookFeet); setField(L, "lookAddons", outfit.lookAddons); setField(L, "lookMount", outfit.lookMount); + setField(L, "lookMountHead", outfit.lookMountHead); + setField(L, "lookMountBody", outfit.lookMountBody); + setField(L, "lookMountLegs", outfit.lookMountLegs); + setField(L, "lookMountFeet", outfit.lookMountFeet); } void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit* outfit) @@ -1355,6 +1364,50 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ME_RAGIAZ_BONECAPSULE) registerEnum(CONST_ME_CRITICAL_DAMAGE) registerEnum(CONST_ME_PLUNGING_FISH) + registerEnum(CONST_ME_BLUECHAIN) + registerEnum(CONST_ME_ORANGECHAIN) + registerEnum(CONST_ME_GREENCHAIN) + registerEnum(CONST_ME_PURPLECHAIN) + registerEnum(CONST_ME_GREYCHAIN) + registerEnum(CONST_ME_YELLOWCHAIN) + registerEnum(CONST_ME_YELLOWSPARKLES) + registerEnum(CONST_ME_FAEEXPLOSION) + registerEnum(CONST_ME_FAECOMING) + registerEnum(CONST_ME_FAEGOING) + registerEnum(CONST_ME_BIGCLOUDSSINGLESPACE) + registerEnum(CONST_ME_STONESSINGLESPACE) + registerEnum(CONST_ME_BLUEGHOST) + registerEnum(CONST_ME_POINTOFINTEREST) + registerEnum(CONST_ME_MAPEFFECT) + registerEnum(CONST_ME_PINKSPARK) + registerEnum(CONST_ME_FIREWORK_GREEN) + registerEnum(CONST_ME_FIREWORK_ORANGE) + registerEnum(CONST_ME_FIREWORK_PURPLE) + registerEnum(CONST_ME_FIREWORK_TURQUOISE) + registerEnum(CONST_ME_THECUBE) + registerEnum(CONST_ME_DRAWINK) + registerEnum(CONST_ME_PRISMATICSPARKLES) + registerEnum(CONST_ME_THAIAN) + registerEnum(CONST_ME_THAIANGHOST) + registerEnum(CONST_ME_GHOSTSMOKE) + registerEnum(CONST_ME_FLOATINGBLOCK) + registerEnum(CONST_ME_BLOCK) + registerEnum(CONST_ME_ROOTING) + registerEnum(CONST_ME_SUNPRIEST) + registerEnum(CONST_ME_WERELION) + registerEnum(CONST_ME_GHOSTLYSCRATCH) + registerEnum(CONST_ME_GHOSTLYBITE) + registerEnum(CONST_ME_BIGSCRATCHING) + registerEnum(CONST_ME_SLASH) + registerEnum(CONST_ME_BITE) + registerEnum(CONST_ME_CHIVALRIOUSCHALLENGE) + registerEnum(CONST_ME_DIVINEDAZZLE) + registerEnum(CONST_ME_ELECTRICALSPARK) + registerEnum(CONST_ME_PURPLETELEPORT) + registerEnum(CONST_ME_REDTELEPORT) + registerEnum(CONST_ME_ORANGETELEPORT) + registerEnum(CONST_ME_GREYTELEPORT) + registerEnum(CONST_ME_LIGHTBLUETELEPORT) registerEnum(CONST_ANI_NONE) registerEnum(CONST_ANI_SPEAR) @@ -1407,6 +1460,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ANI_ENVENOMEDARROW) registerEnum(CONST_ANI_GLOOTHSPEAR) registerEnum(CONST_ANI_SIMPLEARROW) + registerEnum(CONST_ANI_LEAFSTAR) + registerEnum(CONST_ANI_DIAMONDARROW) + registerEnum(CONST_ANI_SPECTRALBOLT) + registerEnum(CONST_ANI_ROYALSTAR) registerEnum(CONST_ANI_WEAPONTYPE) registerEnum(CONST_PROP_BLOCKSOLID) @@ -1447,6 +1504,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(CREATURE_EVENT_MANACHANGE) registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) + registerEnum(CREATURE_ID_MIN) + registerEnum(CREATURE_ID_MAX) + registerEnum(GAME_STATE_STARTUP) registerEnum(GAME_STATE_INIT) registerEnum(GAME_STATE_NORMAL) @@ -1455,11 +1515,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(GAME_STATE_CLOSING) registerEnum(GAME_STATE_MAINTAIN) - registerEnum(MESSAGE_STATUS_CONSOLE_BLUE) - registerEnum(MESSAGE_STATUS_CONSOLE_RED) registerEnum(MESSAGE_STATUS_DEFAULT) registerEnum(MESSAGE_STATUS_WARNING) registerEnum(MESSAGE_EVENT_ADVANCE) + registerEnum(MESSAGE_STATUS_WARNING2) registerEnum(MESSAGE_STATUS_SMALL) registerEnum(MESSAGE_INFO_DESCR) registerEnum(MESSAGE_DAMAGE_DEALT) @@ -1470,12 +1529,20 @@ void LuaScriptInterface::registerFunctions() registerEnum(MESSAGE_HEALED_OTHERS) registerEnum(MESSAGE_EXPERIENCE_OTHERS) registerEnum(MESSAGE_EVENT_DEFAULT) + registerEnum(MESSAGE_LOOT) + registerEnum(MESSAGE_TRADE) registerEnum(MESSAGE_GUILD) registerEnum(MESSAGE_PARTY_MANAGEMENT) registerEnum(MESSAGE_PARTY) - registerEnum(MESSAGE_EVENT_ORANGE) - registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) - registerEnum(MESSAGE_LOOT) + registerEnum(MESSAGE_REPORT) + registerEnum(MESSAGE_HOTKEY_PRESSED) + registerEnum(MESSAGE_MARKET) + registerEnum(MESSAGE_BEYOND_LAST) + registerEnum(MESSAGE_TOURNAMENT_INFO) + registerEnum(MESSAGE_ATTENTION) + registerEnum(MESSAGE_BOOSTED_CREATURE) + registerEnum(MESSAGE_OFFLINE_TRAINING) + registerEnum(MESSAGE_TRANSACTION) registerEnum(CREATURETYPE_PLAYER) registerEnum(CREATURETYPE_MONSTER) @@ -1522,6 +1589,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_WRAPID) registerEnum(ITEM_ATTRIBUTE_STOREITEM) registerEnum(ITEM_ATTRIBUTE_ATTACK_SPEED) + registerEnum(ITEM_ATTRIBUTE_OPENCONTAINER) registerEnum(ITEM_TYPE_DEPOT) registerEnum(ITEM_TYPE_MAILBOX) @@ -1708,7 +1776,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(TALKTYPE_PRIVATE_TO) registerEnum(TALKTYPE_CHANNEL_Y) registerEnum(TALKTYPE_CHANNEL_O) + registerEnum(TALKTYPE_SPELL) registerEnum(TALKTYPE_PRIVATE_NP) + registerEnum(TALKTYPE_PRIVATE_NP_CONSOLE) registerEnum(TALKTYPE_PRIVATE_PN) registerEnum(TALKTYPE_BROADCAST) registerEnum(TALKTYPE_CHANNEL_R1) @@ -1716,6 +1786,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(TALKTYPE_PRIVATE_RED_TO) registerEnum(TALKTYPE_MONSTER_SAY) registerEnum(TALKTYPE_MONSTER_YELL) + registerEnum(TALKTYPE_POTION) registerEnum(TEXTCOLOR_BLUE) registerEnum(TEXTCOLOR_LIGHTGREEN) @@ -1815,7 +1886,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(SPEECHBUBBLE_NORMAL) registerEnum(SPEECHBUBBLE_TRADE) registerEnum(SPEECHBUBBLE_QUEST) - registerEnum(SPEECHBUBBLE_QUESTTRADER) + registerEnum(SPEECHBUBBLE_COMPASS) + registerEnum(SPEECHBUBBLE_NORMAL2) + registerEnum(SPEECHBUBBLE_NORMAL3) + registerEnum(SPEECHBUBBLE_HIRELING) // Use with player:addMapMark registerEnum(MAPMARK_TICK) @@ -7514,7 +7588,14 @@ int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) } pushBoolean(L, creature->setMaster(getCreature(L, 2))); - g_game.updateCreatureType(creature); + + // update summon icon + SpectatorVec spectators; + g_game.map.getSpectators(spectators, creature->getPosition(), true, true); + + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendUpdateTileCreature(creature); + } return 1; } @@ -8177,7 +8258,7 @@ int LuaScriptInterface::luaPlayerCreate(lua_State* L) Player* player; if (isNumber(L, 2)) { uint32_t id = getNumber(L, 2); - if (id >= 0x10000000 && id <= Player::playerAutoID) { + if (id >= CREATURE_ID_MIN && id <= Player::playerIDLimit) { player = g_game.getPlayerByID(id); } else { player = g_game.getPlayerByGUID(id); diff --git a/src/monster.cpp b/src/monster.cpp index a95b8f0cb9..a779b95c23 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -33,7 +33,7 @@ extern ConfigManager g_config; int32_t Monster::despawnRange; int32_t Monster::despawnRadius; -uint32_t Monster::monsterAutoID = 0x40000000; +uint32_t Monster::monsterAutoID = 0x21000000; Monster* Monster::createMonster(const std::string& name) { diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp index e3f7f4a7b3..055a8cac32 100644 --- a/src/networkmessage.cpp +++ b/src/networkmessage.cpp @@ -101,16 +101,25 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count) add(it.clientId); - addByte(0xFF); // MARK_UNMARKED - if (it.stackable) { addByte(count); } else if (it.isSplash() || it.isFluidContainer()) { addByte(fluidMap[count & 7]); + } else if (it.isContainer()) { + addByte(0x00); // assigned loot container icon + addByte(0x00); // quiver ammo count } - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + // podium (placeholder) + // to do: read podium flag from otb + if (it.clientId == 35973 || it.clientId == 35974) { + add(0); //looktype + //4x byte colors if not 0 + add(0); //lookmount + //4x byte colors if not 0 + + addByte(2); //direction + addByte(0x01); //is visible (bool) } } @@ -119,7 +128,6 @@ void NetworkMessage::addItem(const Item* item) const ItemType& it = Item::items[item->getID()]; add(it.clientId); - addByte(0xFF); // MARK_UNMARKED if (it.stackable) { addByte(std::min(0xFF, item->getItemCount())); @@ -127,8 +135,21 @@ void NetworkMessage::addItem(const Item* item) addByte(fluidMap[item->getFluidType() & 7]); } - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + if (it.isContainer()) { + addByte(0x00); // assigned loot container icon + addByte(0x00); // quiver ammo count + } + + // podium (placeholder) + // to do: read podium flag from otb + if (it.clientId == 35973 || it.clientId == 35974) { + add(0); //looktype + //4x byte colors if not 0 + add(0); //lookmount + //4x byte colors if not 0 + + addByte(2); //direction + addByte(0x01); //is visible (bool) } } diff --git a/src/npc.cpp b/src/npc.cpp index 0479821f93..2b98d3602c 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -26,7 +26,7 @@ extern Game g_game; extern LuaEnvironment g_luaEnvironment; -uint32_t Npc::npcAutoID = 0x80000000; +uint32_t Npc::npcAutoID = 0x20000000; NpcScriptInterface* Npc::scriptInterface = nullptr; void Npcs::reload() diff --git a/src/outputmessage.h b/src/outputmessage.h index 6e749243f3..17c3465da4 100644 --- a/src/outputmessage.h +++ b/src/outputmessage.h @@ -43,9 +43,11 @@ class OutputMessage : public NetworkMessage add_header(info.length); } - void addCryptoHeader(bool addChecksum) { - if (addChecksum) { + void addCryptoHeader(checksumMode_t mode, uint32_t& sequence) { + if (mode == CHECKSUM_ADLER) { add_header(adlerChecksum(buffer + outputBufferStart, info.length)); + } else if (mode == CHECKSUM_SEQUENCE) { + add_header(sequence++); } writeMessageLength(); diff --git a/src/party.cpp b/src/party.cpp index 04281572bf..36ac4467ee 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -47,7 +47,6 @@ void Party::disband() currentLeader->setParty(nullptr); currentLeader->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(currentLeader); - g_game.updatePlayerHelpers(*currentLeader); currentLeader->sendCreatureSkull(currentLeader); currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); @@ -72,7 +71,6 @@ void Party::disband() member->sendCreatureSkull(currentLeader); currentLeader->sendCreatureSkull(member); - g_game.updatePlayerHelpers(*member); } memberList.clear(); delete this; @@ -114,12 +112,10 @@ bool Party::leaveParty(Player* player) player->setParty(nullptr); player->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(player); - g_game.updatePlayerHelpers(*player); for (Player* member : memberList) { member->sendCreatureSkull(player); player->sendPlayerPartyIcons(member); - g_game.updatePlayerHelpers(*member); } leader->sendCreatureSkull(player); @@ -209,8 +205,6 @@ bool Party::joinParty(Player& player) memberList.push_back(&player); - g_game.updatePlayerHelpers(player); - player.removePartyInvitation(this); updateSharedExperience(); @@ -237,12 +231,6 @@ bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) if (empty()) { disband(); - } else { - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - - g_game.updatePlayerHelpers(*leader); } return true; @@ -271,11 +259,6 @@ bool Party::invitePlayer(Player& player) inviteList.push_back(&player); - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*leader); - leader->sendCreatureShield(&player); player.sendCreatureShield(leader); diff --git a/src/player.cpp b/src/player.cpp index 7ebf6fb5be..682240687e 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -46,6 +46,7 @@ extern Events* g_events; MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; +uint32_t Player::playerIDLimit = 0x20000000; Player::Player(ProtocolGame_ptr p) : Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), inbox(new Inbox(ITEM_INBOX)), storeInbox(new StoreInbox(ITEM_STORE_INBOX)), client(std::move(p)) @@ -78,6 +79,21 @@ Player::~Player() setEditHouse(nullptr); } +void Player::setID() { + if (id == 0) { + // allowClones id assignment + if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + id = playerAutoID++; + return; + } + + // normal id assignment + if (guid != 0) { + id = playerAutoID + guid; + } + } +} + bool Player::setVocation(uint16_t vocId) { Vocation* voc = g_vocations.getVocation(vocId); @@ -394,9 +410,9 @@ float Player::getDefenseFactor() const } } -uint16_t Player::getClientIcons() const +uint32_t Player::getClientIcons() const { - uint16_t icons = 0; + uint32_t icons = 0; for (Condition* condition : conditions) { if (!isSuppress(condition->getType())) { icons |= condition->getIcons(); @@ -414,7 +430,7 @@ uint16_t Player::getClientIcons() const icons &= ~ICON_SWORDS; } - // Game client debugs with 10 or more icons + // Game client crash with more than 10 icons // so let's prevent that from happening. std::bitset<20> icon_bitset(static_cast(icons)); for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) { @@ -1033,6 +1049,47 @@ void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) } } +void Player::openSavedContainers() +{ + std::map openContainersList; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + Container* itemContainer = item->getContainer(); + if (itemContainer) { + uint8_t cid = item->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (cid > 0) { + openContainersList.emplace(cid, itemContainer); + } + for (ContainerIterator it = itemContainer->iterator(); it.hasNext(); it.advance()) { + Container* subContainer = (*it)->getContainer(); + if (subContainer) { + uint8_t subcid = (*it)->getIntAttr(ITEM_ATTRIBUTE_OPENCONTAINER); + if (subcid > 0) { + openContainersList.emplace(subcid, subContainer); + } + } + } + } + } + + // fix broken containers when logged in from another location + for (uint8_t i = 0; i < 16; i++) { + client->sendEmptyContainer(i); + client->sendCloseContainer(i); + } + + // send actual containers + for (auto& it : openContainersList) { + addContainer(it.first - 1, it.second); + onSendContainer(it.second); + } +} + void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, const ItemType& oldType, const Item* newItem, const ItemType& newType) { @@ -1093,6 +1150,18 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) bed->wakeUp(this); } + // load mount speed bonus + uint8_t currentMountId = currentOutfit.lookMount; + if (currentMountId != 0) { + Mount* currentMount = g_game.mounts.getMountByID(currentMountId); + if (currentMount) { + g_game.changeSpeed(this, currentMount->speed); + } + } + + // mounted player moved to pz on login, update mount status + onChangeZone(getZone()); + Account account = IOLoginData::loadAccount(accountNumber); if (g_config.getBoolean(ConfigManager::PLAYER_CONSOLE_LOGS)) { @@ -3029,6 +3098,7 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ updateInventoryWeight(); updateItemsLight(); sendStats(); + sendItems(); } if (const Item* item = thing->getItem()) { @@ -3083,6 +3153,7 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int updateInventoryWeight(); updateItemsLight(); sendStats(); + sendItems(); } if (const Item* item = thing->getItem()) { diff --git a/src/player.h b/src/player.h index a6f2b2721a..b035cc8446 100644 --- a/src/player.h +++ b/src/player.h @@ -130,11 +130,7 @@ class Player final : public Creature, public Cylinder return this; } - void setID() override { - if (id == 0) { - id = playerAutoID++; - } - } + void setID() final; static MuteCountMap muteCountMap; @@ -257,7 +253,7 @@ class Player final : public Creature, public Cylinder return storeInbox; } - uint16_t getClientIcons() const; + uint32_t getClientIcons() const; const GuildWarVector& getGuildWarVector() const { return guildWarVector; @@ -833,16 +829,6 @@ class Player final : public Creature, public Cylinder client->sendCreatureShield(creature); } } - void sendCreatureType(uint32_t creatureId, uint8_t creatureType) { - if (client) { - client->sendCreatureType(creatureId, creatureType); - } - } - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) { - if (client) { - client->sendCreatureHelpers(creatureId, helpers); - } - } void sendSpellCooldown(uint8_t spellId, uint32_t time) { if (client) { client->sendSpellCooldown(spellId, time); @@ -876,6 +862,7 @@ class Player final : public Creature, public Cylinder client->sendItems(); } } + void openSavedContainers(); //event methods void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, @@ -1163,6 +1150,10 @@ class Player final : public Creature, public Cylinder void updateRegeneration(); + const std::map& getOpenContainers() const { + return openContainers; + } + private: std::forward_list getMuteConditions() const; @@ -1329,6 +1320,7 @@ class Player final : public Creature, public Cylinder bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; static uint32_t playerAutoID; + static uint32_t playerIDLimit; void updateItemsLight(bool internal = false); int32_t getStepSpeed() const override { diff --git a/src/protocol.cpp b/src/protocol.cpp index 9a3470ca36..b1db3b5e1b 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -60,14 +60,14 @@ bool XTEA_decrypt(NetworkMessage& msg, const xtea::round_keys& key) } -void Protocol::onSendMessage(const OutputMessage_ptr& msg) const +void Protocol::onSendMessage(const OutputMessage_ptr& msg) { if (!rawMessages) { msg->writeMessageLength(); if (encryptionEnabled) { XTEA_encrypt(*msg, key); - msg->addCryptoHeader(checksumEnabled); + msg->addCryptoHeader(checksumMode, sequenceNumber); } } } diff --git a/src/protocol.h b/src/protocol.h index 8bd7d6f240..13e874a636 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -35,7 +35,7 @@ class Protocol : public std::enable_shared_from_this virtual void parsePacket(NetworkMessage&) {} - virtual void onSendMessage(const OutputMessage_ptr& msg) const; + virtual void onSendMessage(const OutputMessage_ptr& msg); void onRecvMessage(NetworkMessage& msg); virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; virtual void onConnect() {} @@ -75,8 +75,8 @@ class Protocol : public std::enable_shared_from_this void setXTEAKey(const xtea::key& key) { this->key = xtea::expand_key(key); } - void disableChecksum() { - checksumEnabled = false; + void setChecksumMode(checksumMode_t newMode) { + checksumMode = newMode; } static bool RSA_decrypt(NetworkMessage& msg); @@ -94,8 +94,9 @@ class Protocol : public std::enable_shared_from_this const ConnectionWeak_ptr connection; xtea::round_keys key; + uint32_t sequenceNumber = 0; bool encryptionEnabled = false; - bool checksumEnabled = true; + checksumMode_t checksumMode = CHECKSUM_ADLER; bool rawMessages = false; }; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index 3db0b0b436..884f8950d2 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -277,8 +277,8 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) player->clearModalWindows(); player->setOperatingSystem(operatingSystem); player->isConnecting = false; - player->client = getThis(); + sendAddCreature(player, player->getPosition(), 0, false); player->lastIP = player->getIP(); player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); @@ -318,28 +318,43 @@ void ProtocolGame::logout(bool displayEffect, bool forced) } } + sendSessionEnd(forced ? SESSION_END_FORCECLOSE : SESSION_END_LOGOUT); disconnect(); g_game.removeCreature(player); } +// Login to the game world request void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) { + // Server is shutting down if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { disconnect(); return; } + // Client type and OS used OperatingSystem_t operatingSystem = static_cast(msg.get()); - version = msg.get(); - msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision + version = msg.get(); // U16 client version + msg.skipBytes(4); // U32 client version + + // String client version + if (version >= 1240) { + if (msg.getLength() - msg.getBufferPosition() > 132) { + msg.getString(); + } + } + + msg.skipBytes(3); // U16 dat revision, U8 preview state + // Disconnect if RSA decrypt fails if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; } + // Get XTEA key xtea::key key; key[0] = msg.get(); key[1] = msg.get(); @@ -348,6 +363,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) enableXTEAEncryption(); setXTEAKey(std::move(key)); + // Enable extended opcode feature for otclient if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { NetworkMessage opcodeMessage; opcodeMessage.addByte(0x32); @@ -356,16 +372,31 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) writeToOutputBuffer(opcodeMessage); } - msg.skipBytes(1); // gamemaster flag + // Change packet verifying mode for QT clients + if (version >= 1111 && operatingSystem >= CLIENTOS_QT_LINUX && operatingSystem <= CLIENTOS_QT_MAC) { + setChecksumMode(CHECKSUM_SEQUENCE); + } + + // Web login skips the character list request so we need to check the client version again + if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR)); + return; + } + + msg.skipBytes(1); // Gamemaster flag std::string sessionKey = msg.getString(); - - auto sessionArgs = explodeString(sessionKey, "\n", 4); + auto sessionArgs = explodeString(sessionKey, "\n", 4); // acc name or email, password, token, timestamp divided by 30 if (sessionArgs.size() != 4) { disconnect(); return; } + if (operatingSystem == CLIENTOS_QT_LINUX) { + msg.getString(); // OS name (?) + msg.getString(); // OS version (?) + } + std::string& accountName = sessionArgs[0]; std::string& password = sessionArgs[1]; std::string& token = sessionArgs[2]; @@ -394,11 +425,6 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) return; } - if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { - disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR)); - return; - } - if (g_game.getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait."); return; @@ -503,6 +529,10 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break; case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break; case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break; + //case 0x2A: break; // bestiary tracker + //case 0x2C: break; // team finder (leader) + //case 0x2D: break; // team finder (member) + //case 0x28: break; // stash withdraw case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode case 0x64: parseAutoWalk(msg); break; case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break; @@ -518,6 +548,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; + //case 0x73: break; // map click(?) case 0x77: parseEquipObject(msg); break; case 0x78: parseThrow(msg); break; case 0x79: parseLookInShop(msg); break; @@ -532,6 +563,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x83: parseUseItemEx(msg); break; case 0x84: parseUseWithCreature(msg); break; case 0x85: parseRotateItem(msg); break; + //case 0x86: break; // podium interaction case 0x87: parseCloseContainer(msg); break; case 0x88: parseUpArrowContainer(msg); break; case 0x89: parseTextWindow(msg); break; @@ -540,6 +572,10 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x8C: parseLookAt(msg); break; case 0x8D: parseLookInBattleList(msg); break; case 0x8E: /* join aggression */ break; + //case 0x8F: break; // quick loot + //case 0x90: break; // loot container + //case 0x91: break; // update loot whitelist + //case 0x92: break; // request locker items case 0x96: parseSay(msg); break; case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; case 0x98: parseOpenChannel(msg); break; @@ -558,20 +594,35 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; case 0xAB: parseChannelInvite(msg); break; case 0xAC: parseChannelExclude(msg); break; + //case 0xB1: break; // request highscores case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; + //case 0xC7: break; // request tournament leaderboard case 0xC9: /* update tile */ break; case 0xCA: parseUpdateContainer(msg); break; case 0xCB: parseBrowseField(msg); break; case 0xCC: parseSeekInContainer(msg); break; + //case 0xCD: break; // request inspect window case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; case 0xD3: parseSetOutfit(msg); break; case 0xD4: parseToggleMount(msg); break; + //case 0xD5: break; // apply imbuement + //case 0xD6: break; // clear imbuement + //case 0xD7: break; // close imbuing window case 0xDC: parseAddVip(msg); break; case 0xDD: parseRemoveVip(msg); break; case 0xDE: parseEditVip(msg); break; + //case 0xDF: break; // premium shop (?) + //case 0xE0: break; // premium shop (?) + //case 0xE1: break; // bestiary 1 + //case 0xE2: break; // bestiary 2 + //case 0xE3: break; // bestiary 3 + //case 0xE4: break; // buy charm rune + //case 0xE5: break; // request character info (cyclopedia) case 0xE6: parseBugReport(msg); break; case 0xE7: /* thank you */ break; case 0xE8: parseDebugAssert(msg); break; + case 0xEE: addGameTask(&Game::playerSay, player->getID(), 0, TALKTYPE_SAY, std::string(), "hi"); break; + //case 0xEF: break; // request store coins transfer case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; case 0xF1: parseQuestLine(msg); break; case 0xF2: parseRuleViolationReport(msg); break; @@ -582,6 +633,11 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0xF7: parseMarketCancelOffer(msg); break; case 0xF8: parseMarketAcceptOffer(msg); break; case 0xF9: parseModalWindowAnswer(msg); break; + //case 0xFA: break; // store window open + //case 0xFB: break; // store window click + //case 0xFC: break; // store window buy + //case 0xFD: break; // store window history 1 + //case 0xFE: break; // store window history 2 default: // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; @@ -595,8 +651,6 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) { - msg.add(0x00); //environmental effects - int32_t count; Item* ground = tile->getGround(); if (ground) { @@ -842,6 +896,8 @@ void ProtocolGame::parseAutoWalk(NetworkMessage& msg) void ProtocolGame::parseSetOutfit(NetworkMessage& msg) { + uint8_t outfitType = msg.getByte(); + Outfit_t newOutfit; newOutfit.lookType = msg.get(); newOutfit.lookHead = msg.getByte(); @@ -849,8 +905,57 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookLegs = msg.getByte(); newOutfit.lookFeet = msg.getByte(); newOutfit.lookAddons = msg.getByte(); - newOutfit.lookMount = msg.get(); - addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); + + // Set outfit window + if (outfitType == 0) { + newOutfit.lookMount = msg.get(); + if (newOutfit.lookMount != 0) { + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + } else { + msg.skipBytes(4); + + // prevent mount color settings from resetting + const Outfit_t& currentOutfit = player->getCurrentOutfit(); + newOutfit.lookMountHead = currentOutfit.lookMountHead; + newOutfit.lookMountBody = currentOutfit.lookMountBody; + newOutfit.lookMountLegs = currentOutfit.lookMountLegs; + newOutfit.lookMountFeet = currentOutfit.lookMountFeet; + } + + msg.get(); // familiar looktype + addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); + + // Store "try outfit" window + } else if (outfitType == 1) { + newOutfit.lookMount = 0; + // mount colors or store offerId (needs testing) + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + //player->? (open store?) + + // Podium interaction + } else if (outfitType == 2) { + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + newOutfit.lookMount = msg.get(); + newOutfit.lookMountHead = msg.getByte(); + newOutfit.lookMountBody = msg.getByte(); + newOutfit.lookMountLegs = msg.getByte(); + newOutfit.lookMountFeet = msg.getByte(); + + msg.get(); // familiar looktype + msg.getByte(); // outfit direction + msg.getByte(); // show outfit (bool) + + //apply to podium + //player->getID(), newOutfit, pos, stackpos, spriteId, podiumVisible, direction + } } void ProtocolGame::parseToggleMount(NetworkMessage& msg) @@ -999,8 +1104,9 @@ void ProtocolGame::parseFollow(NetworkMessage& msg) void ProtocolGame::parseEquipObject(NetworkMessage& msg) { + // hotkey equip (?) uint16_t spriteId = msg.get(); - // msg.get(); + // msg.get(); // bool smartMode (?) addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerEquipItem, player->getID(), spriteId); } @@ -1206,6 +1312,7 @@ void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg) uint32_t price = msg.get(); bool anonymous = (msg.getByte() != 0); addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous); + sendStoreBalance(); } void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) @@ -1213,6 +1320,7 @@ void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) uint32_t timestamp = msg.get(); uint16_t counter = msg.get(); addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter); + sendStoreBalance(); } void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) @@ -1294,6 +1402,16 @@ void ProtocolGame::sendWorldLight(LightInfo lightInfo) writeToOutputBuffer(msg); } +void ProtocolGame::sendWorldTime() +{ + int16_t time = g_game.getWorldTime(); + NetworkMessage msg; + msg.addByte(0xEF); + msg.addByte(time / 60); // hour + msg.addByte(time % 60); // min + writeToOutputBuffer(msg); +} + void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { if (!canSee(creature)) { @@ -1337,24 +1455,6 @@ void ProtocolGame::sendCreatureSkull(const Creature* creature) writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) -{ - NetworkMessage msg; - msg.addByte(0x95); - msg.add(creatureId); - msg.addByte(creatureType); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) -{ - NetworkMessage msg; - msg.addByte(0x94); - msg.add(creatureId); - msg.add(helpers); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) { if (!canSee(creature)) { @@ -1381,6 +1481,7 @@ void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const st { NetworkMessage msg; msg.addByte(0xDD); + msg.addByte(0x00); // unknown msg.addPosition(pos); msg.addByte(markType); msg.addString(desc); @@ -1393,6 +1494,7 @@ void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) msg.addByte(0x28); msg.addByte(0x00); msg.addByte(unfairFightReduction); + msg.addByte(0x00); // can use death redemption (bool) writeToOutputBuffer(msg); } @@ -1403,6 +1505,33 @@ void ProtocolGame::sendStats() writeToOutputBuffer(msg); } +void ProtocolGame::sendClientFeatures() +{ + NetworkMessage msg; + msg.addByte(0x17); + + msg.add(player->getID()); + msg.add(50); // beat duration + + msg.addDouble(Creature::speedA, 3); + msg.addDouble(Creature::speedB, 3); + msg.addDouble(Creature::speedC, 3); + + // can report bugs? + msg.addByte(player->getAccountType() >= ACCOUNT_TYPE_TUTOR ? 0x01 : 0x00); + + msg.addByte(0x00); // can change pvp framing option + msg.addByte(0x00); // expert mode button enabled + + msg.add(0x00); // store images url (string or u16 0x00) + msg.add(25); // premium coin package size + + msg.addByte(0x00); // exiva button enabled (bool) + msg.addByte(0x00); // Tournament button (bool) + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendBasicData() { NetworkMessage msg; @@ -1415,10 +1544,15 @@ void ProtocolGame::sendBasicData() msg.add(0); } msg.addByte(player->getVocation()->getClientId()); - msg.add(0xFF); // number of known spells + msg.addByte(0x00); // is prey system enabled (bool) + + // unlock spells on action bar + msg.add(0xFF); for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) { msg.addByte(spellId); } + + msg.addByte(0x00); // is magic shield active (bool) writeToOutputBuffer(msg); } @@ -1536,11 +1670,11 @@ void ProtocolGame::sendChannelMessage(const std::string& author, const std::stri writeToOutputBuffer(msg); } -void ProtocolGame::sendIcons(uint16_t icons) +void ProtocolGame::sendIcons(uint32_t icons) { NetworkMessage msg; msg.addByte(0xA2); - msg.add(icons); + msg.add(icons); writeToOutputBuffer(msg); } @@ -1560,9 +1694,8 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h } msg.addByte(container->capacity()); - msg.addByte(hasParent ? 0x01 : 0x00); - + msg.addByte(0x00); // show search icon (boolean) msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination @@ -1582,12 +1715,39 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h writeToOutputBuffer(msg); } +void ProtocolGame::sendEmptyContainer(uint8_t cid) +{ + NetworkMessage msg; + msg.addByte(0x6E); + + msg.addByte(cid); + + msg.addItem(ITEM_BAG, 1); + msg.addString("Placeholder"); + + msg.addByte(8); + msg.addByte(0x00); + msg.addByte(0x00); + msg.addByte(0x01); + msg.addByte(0x00); + msg.add(0); + msg.add(0); + msg.addByte(0x00); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) { NetworkMessage msg; msg.addByte(0x7A); msg.addString(npc->getName()); + // currency displayed in trade window (currently only gold supported) + // if item other than gold coin is sent, the shop window takes information + // about currency amount from player items packet (the one that updates action bars) + msg.add(Item::items[ITEM_GOLD_COIN].clientId); + msg.addString(""); // doesn't show anywhere, could be used in otclient for currency name + uint16_t itemsToSend = std::min(itemList.size(), std::numeric_limits::max()); msg.add(itemsToSend); @@ -1608,9 +1768,14 @@ void ProtocolGame::sendCloseShop() void ProtocolGame::sendSaleItemList(const std::list& shop) { + uint64_t playerBank = player->getBankBalance(); + uint64_t playerMoney = player->getMoney(); + sendResourceBalance(RESOURCE_BANK_BALANCE, playerBank); + sendResourceBalance(RESOURCE_GOLD_EQUIPPED, playerMoney); + NetworkMessage msg; msg.addByte(0x7B); - msg.add(player->getMoney() + player->getBankBalance()); + msg.add(playerBank + playerMoney); // deprecated and ignored by QT client. OTClient still uses it. std::map saleMap; @@ -1687,12 +1852,33 @@ void ProtocolGame::sendSaleItemList(const std::list& shop) writeToOutputBuffer(msg); } +void ProtocolGame::sendResourceBalance(const ResourceTypes_t resourceType, uint64_t amount) +{ + NetworkMessage msg; + msg.addByte(0xEE); + msg.addByte(resourceType); + msg.add(amount); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendStoreBalance() +{ + NetworkMessage msg; + msg.addByte(0xDF); + msg.addByte(0x01); + + // placeholder packet / to do + msg.add(0); // total store coins (transferable + non-t) + msg.add(0); // transferable store coins + msg.add(0); // reserved auction coins + msg.add(0); // tournament coins + writeToOutputBuffer(msg); +} + void ProtocolGame::sendMarketEnter(uint32_t depotId) { NetworkMessage msg; msg.addByte(0xF6); - - msg.add(player->getBankBalance()); msg.addByte(std::min(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits::max())); DepotChest* depotChest = player->getDepotChest(depotId, false); @@ -1745,6 +1931,10 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId) } writeToOutputBuffer(msg); + + sendResourceBalance(RESOURCE_BANK_BALANCE, player->getBankBalance()); + sendResourceBalance(RESOURCE_GOLD_EQUIPPED, player->getMoney()); + sendStoreBalance(); } void ProtocolGame::sendMarketLeave() @@ -1779,6 +1969,7 @@ void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& msg.addString(offer.playerName); } + sendStoreBalance(); writeToOutputBuffer(msg); } @@ -2074,6 +2265,13 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId) msg.add(0x00); } + //string or u16 0x00 + msg.add(0x00); // magic shield capacity + msg.add(0x00); // cleave + msg.add(0x00); // damage reflection + msg.add(0x00); // perfect shot + msg.add(0x00); // imbuing slots + MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId); if (statistics) { msg.addByte(0x01); @@ -2219,6 +2417,7 @@ void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, msg.add(++statementId); msg.addString(creature->getName()); + msg.addByte(0x00); // "(Traded)" suffix after player name //Add level only for players if (const Player* speaker = creature->getPlayer()) { @@ -2247,8 +2446,11 @@ void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, co msg.add(++statementId); if (!creature) { msg.add(0x00); + msg.addByte(0x00); // "(Traded)" suffix after player name } else { msg.addString(creature->getName()); + msg.addByte(0x00); // "(Traded)" suffix after player name + //Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); @@ -2271,9 +2473,11 @@ void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, msg.add(++statementId); if (speaker) { msg.addString(speaker->getName()); + msg.addByte(0x00); // "(Traded)" suffix after player name msg.add(speaker->getLevel()); } else { msg.add(0x00); + msg.addByte(0x00); // "(Traded)" suffix after player name } msg.addByte(type); msg.addString(text); @@ -2330,10 +2534,13 @@ void ProtocolGame::sendPingBack() void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type) { NetworkMessage msg; - msg.addByte(0x85); + msg.addByte(0x83); msg.addPosition(from); - msg.addPosition(to); + msg.addByte(MAGIC_EFFECTS_CREATE_DISTANCEEFFECT); msg.addByte(type); + msg.addByte(static_cast(static_cast(static_cast(to.x) - static_cast(from.x)))); + msg.addByte(static_cast(static_cast(static_cast(to.y) - static_cast(from.y)))); + msg.addByte(MAGIC_EFFECTS_END_LOOP); writeToOutputBuffer(msg); } @@ -2346,7 +2553,9 @@ void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) NetworkMessage msg; msg.addByte(0x83); msg.addPosition(pos); + msg.addByte(MAGIC_EFFECTS_CREATE_EFFECT); msg.addByte(type); + msg.addByte(MAGIC_EFFECTS_END_LOOP); writeToOutputBuffer(msg); } @@ -2545,58 +2754,46 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos return; } - NetworkMessage msg; - msg.addByte(0x17); - - msg.add(player->getID()); - msg.add(0x32); // beat duration (50) - - msg.addDouble(Creature::speedA, 3); - msg.addDouble(Creature::speedB, 3); - msg.addDouble(Creature::speedC, 3); - - // can report bugs? - if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { - msg.addByte(0x01); - } else { - msg.addByte(0x00); - } - - msg.addByte(0x00); // can change pvp framing option - msg.addByte(0x00); // expert mode button enabled - - msg.add(0x00); // URL (string) to ingame store images - msg.add(25); // premium coin package size + // send player stats + sendStats(); // hp, cap, level, xp rate, etc. + sendSkills(); // skills and special skills + player->sendIcons(); // active conditions - writeToOutputBuffer(msg); + // send client info + sendClientFeatures(); // player speed, bug reports, store url, pvp mode, etc + sendBasicData(); // premium account, vocation, known spells, prey system status, magic shield status + sendItems(); // send carried items for action bars + // enter world and send game screen sendPendingStateEntered(); sendEnterWorld(); sendMapDescription(pos); + // send login effect if (isLogin) { sendMagicEffect(pos, CONST_ME_TELEPORT); } + // send equipment for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { sendInventoryItem(static_cast(i), player->getInventoryItem(static_cast(i))); } + // send store inbox sendInventoryItem(CONST_SLOT_STORE_INBOX, player->getStoreInbox()->getItem()); - sendStats(); - sendSkills(); - - //gameworld light-settings + // gameworld time of the day sendWorldLight(g_game.getWorldLightInfo()); + sendWorldTime(); - //player light level + // player light level sendCreatureLight(creature); + // player vip list sendVIPEntries(); - sendBasicData(); - player->sendIcons(); + // opened containers + player->openSavedContainers(); } void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) @@ -2682,23 +2879,27 @@ void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) writeToOutputBuffer(msg); } +// to do: make it lightweight, update each time player gets/loses an item void ProtocolGame::sendItems() { NetworkMessage msg; msg.addByte(0xF5); - const std::vector& inventory = Item::items.getInventory(); + // find all items carried by character (itemId, amount) + std::map inventory; + player->getAllItemTypeCount(inventory); + msg.add(inventory.size() + 11); for (uint16_t i = 1; i <= 11; i++) { - msg.add(i); - msg.addByte(0); //always 0 + msg.add(i); // slotId + msg.addByte(0); // always 0 msg.add(1); // always 1 } - for (auto clientId : inventory) { - msg.add(clientId); - msg.addByte(0); //always 0 - msg.add(1); + for (const auto& item : inventory) { + msg.add(Item::items[item.first].clientId); // item clientId + msg.addByte(0); // always 0 + msg.add(item.second); // count } writeToOutputBuffer(msg); @@ -2761,6 +2962,8 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t ma msg.add(0x00); } + msg.addByte(0x00); // "(traded)" suffix after player name (bool) + time_t writtenDate = item->getDate(); if (writtenDate != 0) { msg.addString(formatDateShort(writtenDate)); @@ -2779,8 +2982,9 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const msg.addItem(itemId, 1); msg.add(text.size()); msg.addString(text); - msg.add(0x00); - msg.add(0x00); + msg.add(0x00); // writer name + msg.addByte(0x00); // "(traded)" byte + msg.add(0x00); // date writeToOutputBuffer(msg); } @@ -2805,6 +3009,8 @@ void ProtocolGame::sendOutfitWindow() msg.addByte(0xC8); Outfit_t currentOutfit = player->getDefaultOutfit(); + bool mounted = currentOutfit.lookMount != 0; + if (currentOutfit.lookType == 0) { Outfit_t newOutfit; newOutfit.lookType = outfits.front().lookType; @@ -2818,6 +3024,16 @@ void ProtocolGame::sendOutfitWindow() AddOutfit(msg, currentOutfit); + // mount color bytes are required here regardless of having one + if (currentOutfit.lookMount == 0){ + msg.addByte(currentOutfit.lookMountHead); + msg.addByte(currentOutfit.lookMountBody); + msg.addByte(currentOutfit.lookMountLegs); + msg.addByte(currentOutfit.lookMountFeet); + } + + msg.add(0); // current familiar looktype + std::vector protocolOutfits; if (player->isAccessPlayer()) { static const std::string gamemasterOutfitName = "Gamemaster"; @@ -2832,16 +3048,14 @@ void ProtocolGame::sendOutfitWindow() } protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - if (protocolOutfits.size() == std::numeric_limits::max()) { // Game client currently doesn't allow more than 255 outfits - break; - } } - msg.addByte(protocolOutfits.size()); + msg.add(protocolOutfits.size()); for (const ProtocolOutfit& outfit : protocolOutfits) { msg.add(outfit.lookType); msg.addString(outfit.name); msg.addByte(outfit.addons); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId), 0x02 golden outfit tooltip (hardcoded) } std::vector mounts; @@ -2851,12 +3065,21 @@ void ProtocolGame::sendOutfitWindow() } } - msg.addByte(mounts.size()); + msg.add(mounts.size()); for (const Mount* mount : mounts) { msg.add(mount->clientId); msg.addString(mount->name); + msg.addByte(0x00); // mode: 0x00 - available, 0x01 store (requires U32 store offerId) } + msg.add(0x00); // familiars.size() + // size > 0 + // U16 looktype + // String name + // 0x00 // mode: 0x00 - available, 0x01 store (requires U32 store offerId) + + msg.addByte(0x00); //Try outfit mode (?) + msg.addByte(mounted ? 0x01 : 0x00); writeToOutputBuffer(msg); } @@ -2879,6 +3102,7 @@ void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::st msg.add(std::min(10, icon)); msg.addByte(notify ? 0x01 : 0x00); msg.addByte(status); + msg.addByte(0x00); // vipGroups (placeholder) writeToOutputBuffer(msg); } @@ -2945,12 +3169,32 @@ void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) writeToOutputBuffer(msg); } +void ProtocolGame::sendSessionEnd(SessionEndTypes_t reason) +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x18); + output->addByte(reason); + send(output); +} + ////////////// Add common messages void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) { CreatureType_t creatureType = creature->getType(); - const Player* otherPlayer = creature->getPlayer(); + const Player* masterPlayer = nullptr; + uint32_t masterId = 0; + + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + if (master) { + masterPlayer = master->getPlayer(); + if (masterPlayer) { + masterId = master->getID(); + creatureType = CREATURETYPE_SUMMON_OWN; + } + } + } if (known) { msg.add(0x62); @@ -2959,8 +3203,13 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(0x61); msg.add(remove); msg.add(creature->getID()); - msg.addByte(creatureType); - msg.addString(creature->getName()); + msg.addByte(creature->isHealthHidden() ? CREATURETYPE_HIDDEN : creatureType); + + if (creatureType == CREATURETYPE_SUMMON_OWN) { + msg.add(masterId); + } + + msg.addString(creature->isHealthHidden() ? "" : creature->getName()); } if (creature->isHealthHidden()) { @@ -2972,7 +3221,8 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.addByte(creature->getDirection()); if (!creature->isInGhostMode() && !creature->isInvisible()) { - AddOutfit(msg, creature->getCurrentOutfit()); + const Outfit_t& outfit = creature->getCurrentOutfit(); + AddOutfit(msg, outfit); } else { static Outfit_t outfit; AddOutfit(msg, outfit); @@ -2984,6 +3234,15 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(creature->getStepSpeed() / 2); + msg.addByte(0x00); //creature debuffs, to do + /* + if (icon != CREATUREICON_NONE) { + msg.addByte(icon); + msg.addByte(1); + msg.add(0); + } + */ + msg.addByte(player->getSkullClient(creature)); msg.addByte(player->getPartyShield(otherPlayer)); @@ -2991,29 +3250,25 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.addByte(player->getGuildEmblem(otherPlayer)); } - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - const Player* masterPlayer = master->getPlayer(); - if (masterPlayer) { - if (masterPlayer == player) { - creatureType = CREATURETYPE_SUMMON_OWN; - } else { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } + // Creature type and summon emblem + msg.addByte(creature->isHealthHidden() ? CREATURETYPE_HIDDEN : creatureType); + if (creatureType == CREATURETYPE_SUMMON_OWN) { + msg.add(masterId); + } + + // Player vocation info + if (creatureType == CREATURETYPE_PLAYER) { + const Player* otherCreature = creature->getPlayer(); + if (otherCreature) { + msg.addByte(otherCreature->getVocation()->getClientId()); + } else { + msg.addByte(0x00); } } - msg.addByte(creatureType); // Type (for summons) msg.addByte(creature->getSpeechBubble()); msg.addByte(0xFF); // MARK_UNMARKED - - if (otherPlayer) { - msg.add(otherPlayer->getHelpers()); - } else { - msg.add(0x00); - } + msg.addByte(0x00); // inspection type (bool?) msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); } @@ -3026,15 +3281,12 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); msg.add(player->getFreeCapacity()); - msg.add(player->getCapacity()); - msg.add(player->getExperience()); msg.add(player->getLevel()); msg.addByte(player->getLevelPercent()); msg.add(100); // base xp gain rate - msg.add(0); // xp voucher msg.add(0); // low level bonus msg.add(0); // xp boost msg.add(100); // stamina multiplier (100 = x1.0) @@ -3042,14 +3294,8 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(std::min(player->getMana(), std::numeric_limits::max())); msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); - msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); - msg.addByte(std::min(player->getBaseMagicLevel(), std::numeric_limits::max())); - msg.addByte(player->getMagicLevelPercent()); - msg.addByte(player->getSoul()); - msg.add(player->getStaminaMinutes()); - msg.add(player->getBaseSpeed() / 2); Condition* condition = player->getCondition(CONDITION_REGENERATION); @@ -3058,29 +3304,41 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(player->getOfflineTrainingTime() / 60 / 1000); msg.add(0); // xp boost time (seconds) - msg.addByte(0); // enables exp boost in the store + msg.addByte(0x00); // enables exp boost in the store + + msg.add(0); // remaining mana shield + msg.add(0); // total mana shield } void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) { msg.addByte(0xA1); + msg.add(player->getMagicLevel()); + msg.add(player->getBaseMagicLevel()); + msg.add(player->getBaseMagicLevel()); // base + loyalty bonus(?) + msg.add(player->getMagicLevelPercent() * 100); for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max())); msg.add(player->getBaseSkill(i)); - msg.addByte(player->getSkillPercent(i)); + msg.add(player->getBaseSkill(i)); // base + loyalty bonus(?) + msg.add(player->getSkillPercent(i) * 100); } for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - msg.add(std::min(100, player->varSpecialSkills[i])); - msg.add(0); + msg.add(std::min(100, player->varSpecialSkills[i])); // base + bonus special skill + msg.add(0); // base special skill } + + // to do: bonus cap + msg.add(player->getCapacity()); // base + bonus capacity + msg.add(player->getCapacity()); // base capacity } void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) { + // outfit msg.add(outfit.lookType); - if (outfit.lookType != 0) { msg.addByte(outfit.lookHead); msg.addByte(outfit.lookBody); @@ -3091,7 +3349,14 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) msg.addItemId(outfit.lookTypeEx); } + // mount msg.add(outfit.lookMount); + if (outfit.lookMount != 0) { + msg.addByte(outfit.lookMountHead); + msg.addByte(outfit.lookMountBody); + msg.addByte(outfit.lookMountLegs); + msg.addByte(outfit.lookMountFeet); + } } void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) @@ -3233,8 +3498,8 @@ void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) msg.addString(item.realName); msg.add(it.weight); - msg.add(item.buyPrice); - msg.add(item.sellPrice); + msg.add(item.buyPrice == std::numeric_limits::max() ? 0 : item.buyPrice); + msg.add(item.sellPrice == std::numeric_limits::max() ? 0 : item.sellPrice); } void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) diff --git a/src/protocolgame.h b/src/protocolgame.h index 7d9055e973..32e8dcdb58 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -25,6 +25,13 @@ #include "creature.h" #include "tasks.h" +enum SessionEndTypes_t : uint8_t { + SESSION_END_LOGOUT = 0, + SESSION_END_UNKNOWN = 1, // unknown, no difference from logout + SESSION_END_FORCECLOSE = 2, + SESSION_END_UNKNOWN2 = 3, // unknown, no difference from logout +}; + class NetworkMessage; class Player; class Game; @@ -174,7 +181,7 @@ class ProtocolGame final : public Protocol void sendOpenPrivateChannel(const std::string& receiver); void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); - void sendIcons(uint16_t icons); + void sendIcons(uint32_t icons); void sendFYIBox(const std::string& message); void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); @@ -194,6 +201,7 @@ class ProtocolGame final : public Protocol void sendCancelTarget(); void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); void sendStats(); + void sendClientFeatures(); void sendBasicData(); void sendTextMessage(const TextMessage& message); void sendReLoginWindow(uint8_t unfairFightReduction); @@ -204,12 +212,12 @@ class ProtocolGame final : public Protocol void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); void sendCreatureShield(const Creature* creature); void sendCreatureSkull(const Creature* creature); - void sendCreatureType(uint32_t creatureId, uint8_t creatureType); - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers); void sendShop(Npc* npc, const ShopInfoList& itemList); void sendCloseShop(); void sendSaleItemList(const std::list& shop); + void sendResourceBalance(const ResourceTypes_t resourceType, uint64_t amount); + void sendStoreBalance(); void sendMarketEnter(uint32_t depotId); void sendMarketLeave(); void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); @@ -237,6 +245,7 @@ class ProtocolGame final : public Protocol void sendCreatureLight(const Creature* creature); void sendWorldLight(LightInfo lightInfo); + void sendWorldTime(); void sendCreatureSquare(const Creature* creature, SquareColor_t color); @@ -263,6 +272,7 @@ class ProtocolGame final : public Protocol void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); + void sendEmptyContainer(uint8_t cid); void sendCloseContainer(uint8_t cid); //inventory @@ -272,6 +282,9 @@ class ProtocolGame final : public Protocol //messages void sendModalWindow(const ModalWindow& modalWindow); + //session end + void sendSessionEnd(SessionEndTypes_t reason); + //Help functions // translate a tile to client-readable format diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp index 309dce5c89..bb0e099ae4 100644 --- a/src/protocollogin.cpp +++ b/src/protocollogin.cpp @@ -129,6 +129,7 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: disconnect(); } +// Character list request void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) { if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { @@ -139,6 +140,15 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) msg.skipBytes(2); // client OS uint16_t version = msg.get(); + if (version <= 822) { + setChecksumMode(CHECKSUM_DISABLED); + } + + if (version <= 760) { + disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR), version); + return; + } + if (version >= 971) { msg.skipBytes(17); } else { @@ -151,11 +161,6 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) * 1 byte: 0 */ - if (version <= 760) { - disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR), version); - return; - } - if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; diff --git a/src/protocolold.cpp b/src/protocolold.cpp index 49900b2d7d..172922a974 100644 --- a/src/protocolold.cpp +++ b/src/protocolold.cpp @@ -68,7 +68,7 @@ void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) setXTEAKey(std::move(key)); if (version <= 822) { - disableChecksum(); + setChecksumMode(CHECKSUM_DISABLED); } disconnectClient(fmt::format("Only clients with protocol {:s} allowed!", CLIENT_VERSION_STR)); diff --git a/src/raids.cpp b/src/raids.cpp index ab308841aa..15ed944be4 100644 --- a/src/raids.cpp +++ b/src/raids.cpp @@ -324,10 +324,8 @@ bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode) messageType = MESSAGE_INFO_DESCR; } else if (tmpStrValue == "smallstatus") { messageType = MESSAGE_STATUS_SMALL; - } else if (tmpStrValue == "blueconsole") { - messageType = MESSAGE_STATUS_CONSOLE_BLUE; - } else if (tmpStrValue == "redconsole") { - messageType = MESSAGE_STATUS_CONSOLE_RED; + } else if (tmpStrValue == "blueconsole" || tmpStrValue == "redconsole") { + std::cout << "[Notice] Raid: Deprecated type tag for announce event. Using default: " << static_cast(messageType) << std::endl; } else { std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; } diff --git a/src/server.cpp b/src/server.cpp index d0ed14f769..58ca3c5672 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -132,17 +132,14 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error } } -Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const +Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const { uint8_t protocolID = msg.getByte(); for (auto& service : services) { if (protocolID != service->get_protocol_identifier()) { continue; } - - if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { - return service->make_protocol(connection); - } + return service->make_protocol(connection); } return nullptr; } diff --git a/src/server.h b/src/server.h index 60274bd263..4892f7419a 100644 --- a/src/server.h +++ b/src/server.h @@ -76,7 +76,7 @@ class ServicePort : public std::enable_shared_from_this std::string get_protocol_names() const; bool add_service(const Service_ptr& new_svc); - Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const; + Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; void onStopServer(); void onAccept(Connection_ptr connection, const boost::system::error_code& error); diff --git a/src/tools.cpp b/src/tools.cpp index 4e8bc70fa4..f99939dac0 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -464,7 +464,7 @@ Direction getDirectionTo(const Position& from, const Position& to) if (from == to) { return DIRECTION_NONE; } - + Direction dir; int32_t x_offset = Position::getOffsetX(from, to); @@ -590,6 +590,54 @@ MagicEffectNames magicEffectNames = { {"yellowsmoke", CONST_ME_YELLOWSMOKE}, {"greensmoke", CONST_ME_GREENSMOKE}, {"purplesmoke", CONST_ME_PURPLESMOKE}, + {"earlythunder", CONST_ME_EARLY_THUNDER}, + {"bonecapsule", CONST_ME_RAGIAZ_BONECAPSULE}, + {"criticaldamage", CONST_ME_CRITICAL_DAMAGE}, + {"plungingfish", CONST_ME_PLUNGING_FISH}, + {"bluechain", CONST_ME_BLUECHAIN}, + {"orangechain", CONST_ME_ORANGECHAIN}, + {"greenchain", CONST_ME_GREENCHAIN}, + {"purplechain", CONST_ME_PURPLECHAIN}, + {"greychain", CONST_ME_GREYCHAIN}, + {"yellowchain", CONST_ME_YELLOWCHAIN}, + {"yellowsparkles", CONST_ME_YELLOWSPARKLES}, + {"faeexplosion", CONST_ME_FAEEXPLOSION}, + {"faecoming", CONST_ME_FAECOMING}, + {"faegoing", CONST_ME_FAEGOING}, + {"bigcloudssinglespace", CONST_ME_BIGCLOUDSSINGLESPACE}, + {"stonessinglespace", CONST_ME_STONESSINGLESPACE}, + {"blueghost", CONST_ME_BLUEGHOST}, + {"pointofinterest", CONST_ME_POINTOFINTEREST}, + {"mapeffect", CONST_ME_MAPEFFECT}, + {"pinkspark", CONST_ME_PINKSPARK}, + {"greenfirework", CONST_ME_FIREWORK_GREEN}, + {"orangefirework", CONST_ME_FIREWORK_ORANGE}, + {"purplefirework", CONST_ME_FIREWORK_PURPLE}, + {"turquoisefirework", CONST_ME_FIREWORK_TURQUOISE}, + {"thecube", CONST_ME_THECUBE}, + {"drawink", CONST_ME_DRAWINK}, + {"prismaticsparkles", CONST_ME_PRISMATICSPARKLES}, + {"thaian", CONST_ME_THAIAN}, + {"thaianghost", CONST_ME_THAIANGHOST}, + {"ghostsmoke", CONST_ME_GHOSTSMOKE}, + {"floatingblock", CONST_ME_FLOATINGBLOCK}, + {"block", CONST_ME_BLOCK}, + {"rooting", CONST_ME_ROOTING}, + {"sunpriest", CONST_ME_SUNPRIEST}, + {"werelion", CONST_ME_WERELION}, + {"ghostlyscratch", CONST_ME_GHOSTLYSCRATCH}, + {"ghostlybite", CONST_ME_GHOSTLYBITE}, + {"bigscratching", CONST_ME_BIGSCRATCHING}, + {"slash", CONST_ME_SLASH}, + {"bite", CONST_ME_BITE}, + {"chivalriouschallenge", CONST_ME_CHIVALRIOUSCHALLENGE}, + {"divinedazzle", CONST_ME_DIVINEDAZZLE}, + {"electricalspark", CONST_ME_ELECTRICALSPARK}, + {"purpleteleport", CONST_ME_PURPLETELEPORT}, + {"redteleport", CONST_ME_REDTELEPORT}, + {"orangeteleport", CONST_ME_ORANGETELEPORT}, + {"greyteleport", CONST_ME_GREYTELEPORT}, + {"lightblueteleport", CONST_ME_LIGHTBLUETELEPORT}, }; ShootTypeNames shootTypeNames = { @@ -643,6 +691,10 @@ ShootTypeNames shootTypeNames = { {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, {"gloothspear", CONST_ANI_GLOOTHSPEAR}, {"simplearrow", CONST_ANI_SIMPLEARROW}, + {"leafstar", CONST_ANI_LEAFSTAR}, + {"diamondarrow", CONST_ANI_DIAMONDARROW}, + {"spectralbolt", CONST_ANI_SPECTRALBOLT}, + {"royalstar", CONST_ANI_ROYALSTAR}, }; CombatTypeNames combatTypeNames = { @@ -684,6 +736,10 @@ AmmoTypeNames ammoTypeNames = { {"flammingarrow", AMMO_ARROW}, {"shiverarrow", AMMO_ARROW}, {"eartharrow", AMMO_ARROW}, + {"leafstar", AMMO_THROWINGSTAR}, + {"diamondarrow", AMMO_ARROW}, + {"spectralbolt", AMMO_BOLT}, + {"royalstar", AMMO_THROWINGSTAR}, }; WeaponActionNames weaponActionNames = {