mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-04-29 17:19:20 +02:00
added mounts, auras, wings and shop. Packet compression does not work extendedOpCodes does not work but shop is because of extra configuration
This commit is contained in:
parent
b130e66149
commit
1c35d04337
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ vc14/.vs/
|
||||
/SabrehavenOTClient/tibianus.log
|
||||
/800OTClient/otclientv8.log
|
||||
/800OTClient/exports
|
||||
/otclientv8-master
|
||||
|
Binary file not shown.
Binary file not shown.
@ -13,30 +13,28 @@ function updateFeatures(version)
|
||||
end
|
||||
|
||||
-- you can add custom features here, list of them is in the modules\gamelib\const.lua
|
||||
g_game.enableFeature(GamePlayerMarket)
|
||||
--g_game.enableFeature(GameClientPing)
|
||||
--g_game.enableFeature(GameExtendedOpcode)
|
||||
--g_game.enableFeature(GameMinimapLimitedToSingleFloor) -- it will generate minimap only for current floor
|
||||
--g_game.enableFeature(GameSpritesAlphaChannel)
|
||||
|
||||
--if(version >= 770) then
|
||||
g_game.enableFeature(GameLooktypeU16)
|
||||
g_game.enableFeature(GameMessageStatements)
|
||||
g_game.enableFeature(GameLoginPacketEncryption)
|
||||
--end
|
||||
|
||||
--if(version >= 780) then
|
||||
g_game.enableFeature(GamePlayerAddons)
|
||||
g_game.enableFeature(GamePlayerStamina)
|
||||
g_game.enableFeature(GameNewFluids)
|
||||
g_game.enableFeature(GameMessageLevel)
|
||||
g_game.enableFeature(GamePlayerStateU16)
|
||||
g_game.enableFeature(GameNewOutfitProtocol)
|
||||
--end
|
||||
|
||||
--if(version >= 790) then
|
||||
g_game.enableFeature(GameWritableDate)
|
||||
--end
|
||||
|
||||
-- customs
|
||||
--g_game.enableFeature(GameIngameStore)
|
||||
g_game.enableFeature(GamePlayerMarket)
|
||||
|
||||
g_game.enableFeature(GamePlayerMounts)
|
||||
g_game.enableFeature(GameWingsAndAura)
|
||||
g_game.enableFeature(GameOutfitShaders)
|
||||
--g_game.enableFeature(GameExtendedOpcode)
|
||||
--g_game.enableFeature(GamePacketCompression)
|
||||
g_game.enableFeature(GameClientPing)
|
||||
g_game.enableFeature(GameExtendedClientPing)
|
||||
|
||||
if(version >= 840) then
|
||||
g_game.enableFeature(GameProtocolChecksum)
|
||||
|
@ -12,8 +12,8 @@ function init()
|
||||
-- outfit shaders
|
||||
g_shaders.createOutfitShader("outfit_default", "/shaders/outfit_default_vertex", "/shaders/outfit_default_fragment")
|
||||
|
||||
g_shaders.createOutfitShader("outfit_rainbow", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment")
|
||||
g_shaders.addTexture("outfit_rainbow", "/images/shaders/rainbow.png")
|
||||
g_shaders.createOutfitShader("Rainbow Outfit", "/shaders/outfit_rainbow_vertex", "/shaders/outfit_rainbow_fragment")
|
||||
g_shaders.addTexture("Rainbow Outfit", "/images/shaders/rainbow.png")
|
||||
|
||||
-- you can use creature:setOutfitShader("outfit_rainbow") to set shader
|
||||
|
||||
|
@ -21,9 +21,9 @@ local AD = {}
|
||||
local selectedOffer = {}
|
||||
|
||||
local function sendAction(action, data)
|
||||
if not g_game.getFeature(GameExtendedOpcode) then
|
||||
return
|
||||
end
|
||||
--if not g_game.getFeature(GameExtendedOpcode) then
|
||||
-- return
|
||||
--end
|
||||
|
||||
local protocolGame = g_game.getProtocolGame()
|
||||
if data == nil then
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,6 +25,7 @@ find_package(GMP REQUIRED)
|
||||
find_package(PugiXML REQUIRED)
|
||||
find_package(MySQL)
|
||||
find_package(Threads)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
# Selects LuaJIT if user defines or auto-detected
|
||||
if(DEFINED USE_LUAJIT AND NOT USE_LUAJIT)
|
||||
@ -49,7 +50,7 @@ add_subdirectory(src)
|
||||
add_executable(tfs ${tfs_SRC})
|
||||
|
||||
include_directories(${MYSQL_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${PUGIXML_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${Crypto++_INCLUDE_DIR})
|
||||
target_link_libraries(tfs ${MYSQL_CLIENT_LIBS} ${LUA_LIBRARIES} ${Boost_LIBRARIES} ${Boost_FILESYSTEM_LIBRARY} ${PUGIXML_LIBRARIES} ${GMP_LIBRARIES} ${Crypto++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(tfs ${MYSQL_CLIENT_LIBS} ${LUA_LIBRARIES} ${Boost_LIBRARIES} ${Boost_FILESYSTEM_LIBRARY} ${PUGIXML_LIBRARIES} ${ZLIB_LIBRARY} ${GMP_LIBRARIES} ${Crypto++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h")
|
||||
set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
|
||||
|
16
cmake/FindZLIB.cmake
Normal file
16
cmake/FindZLIB.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
# Try to find the ZLIB library
|
||||
# ZLIB_FOUND - system has ZLIB
|
||||
# ZLIB_INCLUDE_DIR - the ZLIB include directory
|
||||
# ZLIB_LIBRARY - the ZLIB library
|
||||
|
||||
FIND_PATH(ZLIB_INCLUDE_DIR NAMES zlib.h)
|
||||
SET(_ZLIB_STATIC_LIBS libz.a libzlib.a zlib1.a)
|
||||
SET(_ZLIB_SHARED_LIBS libz.dll.a zdll zlib zlib1 z)
|
||||
IF(USE_STATIC_LIBS)
|
||||
FIND_LIBRARY(ZLIB_LIBRARY NAMES ${_ZLIB_STATIC_LIBS} ${_ZLIB_SHARED_LIBS})
|
||||
ELSE()
|
||||
FIND_LIBRARY(ZLIB_LIBRARY NAMES ${_ZLIB_SHARED_LIBS} ${_ZLIB_STATIC_LIBS})
|
||||
ENDIF()
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZLIB DEFAULT_MSG ZLIB_LIBRARY ZLIB_INCLUDE_DIR)
|
||||
MARK_AS_ADVANCED(ZLIB_LIBRARY ZLIB_INCLUDE_DIR)
|
@ -51,7 +51,8 @@ allowClones = false
|
||||
serverName = "Tibianus"
|
||||
statusTimeout = 5000
|
||||
replaceKickOnLogin = true
|
||||
maxPacketsPerSecond = -1
|
||||
maxPacketsPerSecond = 50
|
||||
packetCompression = true
|
||||
autoStackCumulatives = true
|
||||
moneyRate = 1
|
||||
|
||||
|
6
data/XML/auras.xml
Normal file
6
data/XML/auras.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<auras>
|
||||
<!-- <aura id="1" clientid="368" name="Widow Queen" speed="20" />
|
||||
<aura id="2" clientid="369" name="Racing Bird" speed="20" />
|
||||
<aura id="3" clientid="370" name="War Bear" speed="20" /> -->
|
||||
</auras>
|
106
data/XML/mounts.xml
Normal file
106
data/XML/mounts.xml
Normal file
@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mounts>
|
||||
<mount id="1" clientid="368" name="Widow Queen" speed="20" premium="no" />
|
||||
<!--
|
||||
<mount id="2" clientid="369" name="Racing Bird" speed="20" premium="yes" />
|
||||
<mount id="3" clientid="370" name="War Bear" speed="20" premium="yes" />
|
||||
<mount id="4" clientid="371" name="Black Sheep" speed="20" premium="yes" />
|
||||
<mount id="5" clientid="372" name="Midnight Panther" speed="20" premium="yes" />
|
||||
<mount id="6" clientid="373" name="Draptor" speed="20" premium="yes" />
|
||||
<mount id="7" clientid="374" name="Titanica" speed="20" premium="yes" />
|
||||
<mount id="8" clientid="375" name="Tin Lizzard" speed="20" premium="yes" />
|
||||
<mount id="9" clientid="376" name="Blazebringer" speed="20" premium="yes" />
|
||||
<mount id="10" clientid="377" name="Rapid Boar" speed="20" premium="yes" />
|
||||
<mount id="11" clientid="378" name="Stampor" speed="20" premium="yes" />
|
||||
<mount id="12" clientid="379" name="Undead Cavebear" speed="20" premium="yes" />
|
||||
<mount id="13" clientid="387" name="Donkey" speed="20" premium="yes" />
|
||||
<mount id="14" clientid="388" name="Tiger Slug" speed="20" premium="yes" />
|
||||
<mount id="15" clientid="389" name="Uniwheel" speed="20" premium="yes" />
|
||||
<mount id="16" clientid="390" name="Crystal Wolf" speed="20" premium="yes" />
|
||||
<mount id="17" clientid="392" name="War Horse" speed="20" premium="yes" />
|
||||
<mount id="18" clientid="401" name="Kingly Deer" speed="20" premium="yes" />
|
||||
<mount id="19" clientid="402" name="Tamed Panda" speed="20" premium="yes" />
|
||||
<mount id="20" clientid="405" name="Dromedary" speed="20" premium="yes" />
|
||||
<mount id="21" clientid="406" name="Scorpion King" speed="20" premium="yes" />
|
||||
<mount id="22" clientid="421" name="Rented Horse" speed="20" premium="no" />
|
||||
<mount id="23" clientid="426" name="Armoured War Horse" speed="20" premium="no" />
|
||||
<mount id="24" clientid="427" name="Shadow Draptor" speed="20" premium="yes" />
|
||||
<mount id="25" clientid="437" name="Rented Horse" speed="20" premium="no" />
|
||||
<mount id="26" clientid="438" name="Rented Horse" speed="20" premium="no" />
|
||||
<mount id="27" clientid="447" name="Lady Bug" speed="20" premium="yes" />
|
||||
<mount id="28" clientid="450" name="Manta Ray" speed="20" premium="yes" />
|
||||
<mount id="29" clientid="502" name="Ironblight" speed="20" premium="yes" />
|
||||
<mount id="30" clientid="503" name="Magma Crawler" speed="20" premium="yes" />
|
||||
<mount id="31" clientid="506" name="Dragonling" speed="20" premium="yes" />
|
||||
<mount id="32" clientid="515" name="Gnarlhound" speed="20" premium="yes" />
|
||||
<mount id="33" clientid="521" name="Crimson Ray" speed="20" premium="no" />
|
||||
<mount id="34" clientid="522" name="Steelbeak" speed="20" premium="no" />
|
||||
<mount id="35" clientid="526" name="Water Buffalo" speed="20" premium="yes" />
|
||||
<mount id="36" clientid="546" name="Tombstinger" speed="20" premium="no" />
|
||||
<mount id="37" clientid="547" name="Platesaurian" speed="20" premium="no" />
|
||||
<mount id="38" clientid="548" name="Ursagrodon" speed="20" premium="yes" />
|
||||
<mount id="39" clientid="559" name="The Hellgrip" speed="20" premium="yes" />
|
||||
<mount id="40" clientid="571" name="Noble Lion" speed="20" premium="yes" />
|
||||
<mount id="41" clientid="572" name="Desert King" speed="20" premium="no" />
|
||||
<mount id="42" clientid="580" name="Shock Head" speed="20" premium="yes" />
|
||||
<mount id="43" clientid="606" name="Walker" speed="20" premium="yes" />
|
||||
<mount id="44" clientid="621" name="Azudocus" speed="20" premium="no" />
|
||||
<mount id="45" clientid="622" name="Carpacosaurus" speed="20" premium="no" />
|
||||
<mount id="46" clientid="624" name="Death Crawler" speed="20" premium="no" />
|
||||
<mount id="47" clientid="626" name="Flamesteed" speed="20" premium="no" />
|
||||
<mount id="48" clientid="627" name="Jade Lion" speed="20" premium="no" />
|
||||
<mount id="49" clientid="628" name="Jade Pincer" speed="20" premium="no" />
|
||||
<mount id="50" clientid="629" name="Nethersteed" speed="20" premium="no" />
|
||||
<mount id="51" clientid="630" name="Tempest" speed="20" premium="no" />
|
||||
<mount id="52" clientid="631" name="Winter King" speed="20" premium="no" />
|
||||
<mount id="53" clientid="644" name="Doombringer" speed="20" premium="no" />
|
||||
<mount id="54" clientid="647" name="Woodland Prince" speed="20" premium="no" />
|
||||
<mount id="55" clientid="648" name="Hailstorm Fury" speed="20" premium="no" />
|
||||
<mount id="56" clientid="649" name="Siegebreaker" speed="20" premium="no" />
|
||||
<mount id="57" clientid="650" name="Poisonbane" speed="20" premium="no" />
|
||||
<mount id="58" clientid="651" name="Blackpelt" speed="20" premium="no" />
|
||||
<mount id="59" clientid="669" name="Golden Dragonfly" speed="20" premium="no" />
|
||||
<mount id="60" clientid="670" name="Steel Bee" speed="20" premium="no" />
|
||||
<mount id="61" clientid="671" name="Copper Fly" speed="20" premium="no" />
|
||||
<mount id="62" clientid="672" name="Tundra Rambler" speed="20" premium="no" />
|
||||
<mount id="63" clientid="673" name="Highland Yak" speed="20" premium="no" />
|
||||
<mount id="64" clientid="674" name="Glacier Vagabond" speed="20" premium="no" />
|
||||
<mount id="65" clientid="688" name="Flying Divan" speed="20" premium="no" />
|
||||
<mount id="66" clientid="689" name="Magic Carpet" speed="20" premium="no" />
|
||||
<mount id="67" clientid="690" name="Floating Kashmir" speed="20" premium="no" />
|
||||
<mount id="68" clientid="691" name="Ringtail Waccoon" speed="20" premium="no" />
|
||||
<mount id="69" clientid="692" name="Night Waccoon" speed="20" premium="no" />
|
||||
<mount id="70" clientid="693" name="Emerald Waccoon" speed="20" premium="no" />
|
||||
<mount id="71" clientid="682" name="Glooth Glider" speed="20" premium="yes" />
|
||||
<mount id="72" clientid="685" name="Shadow Hart" speed="20" premium="no" />
|
||||
<mount id="73" clientid="686" name="Black Stag" speed="20" premium="no" />
|
||||
<mount id="74" clientid="687" name="Emperor Deer" speed="20" premium="no" />
|
||||
<mount id="75" clientid="726" name="Flitterkatzen" speed="20" premium="no" />
|
||||
<mount id="76" clientid="727" name="Venompaw" speed="20" premium="no" />
|
||||
<mount id="77" clientid="728" name="Batcat" speed="20" premium="no" />
|
||||
<mount id="78" clientid="734" name="Sea Devil" speed="20" premium="no" />
|
||||
<mount id="79" clientid="735" name="Coralripper" speed="20" premium="no" />
|
||||
<mount id="80" clientid="736" name="Plumfish" speed="20" premium="no" />
|
||||
<mount id="81" clientid="738" name="Gorongra" speed="20" premium="no" />
|
||||
<mount id="82" clientid="739" name="Noctungra" speed="20" premium="no" />
|
||||
<mount id="83" clientid="740" name="Silverneck" speed="20" premium="no" />
|
||||
<mount id="84" clientid="761" name="Slagsnare" speed="20" premium="no" />
|
||||
<mount id="85" clientid="762" name="Nightstinger" speed="20" premium="no" />
|
||||
<mount id="86" clientid="763" name="Razorcreep" speed="20" premium="no" />
|
||||
<mount id="87" clientid="848" name="Rift Runner" speed="20" premium="yes" />
|
||||
<mount id="88" clientid="849" name="Nightdweller" speed="20" premium="no" />
|
||||
<mount id="89" clientid="850" name="Frostflare" speed="20" premium="no" />
|
||||
<mount id="90" clientid="851" name="Cinderhoof" speed="20" premium="no" />
|
||||
<mount id="91" clientid="868" name="Mouldpincer" speed="20" premium="no" />
|
||||
<mount id="92" clientid="869" name="Bloodcurl" speed="20" premium="no" />
|
||||
<mount id="93" clientid="870" name="Leafscuttler" speed="20" premium="no" />
|
||||
<mount id="94" clientid="883" name="Sparkion" speed="20" premium="yes" />
|
||||
<mount id="95" clientid="886" name="Swamp Snapper" speed="20" premium="no" />
|
||||
<mount id="96" clientid="887" name="Mould Shell" speed="20" premium="no" />
|
||||
<mount id="97" clientid="888" name="Reed Lurker" speed="20" premium="no" />
|
||||
<mount id="98" clientid="889" name="Neon Sparkid" speed="20" premium="yes" />
|
||||
<mount id="99" clientid="890" name="Vortexion" speed="20" premium="yes" />
|
||||
<mount id="100" clientid="901" name="Ivory Fang" speed="20" premium="no" />
|
||||
<mount id="101" clientid="902" name="Shadow Claw" speed="20" premium="no" />
|
||||
<mount id="102" clientid="903" name="Snow Pelt" speed="20" premium="no" /> -->
|
||||
</mounts>
|
4
data/XML/shaders.xml
Normal file
4
data/XML/shaders.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shaders>
|
||||
<shader id="1" name="Rainbow Outfit" premium="no" />
|
||||
</shaders>
|
6
data/XML/wings.xml
Normal file
6
data/XML/wings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<wings>
|
||||
<!-- <wing id="1" clientid="368" name="Widow Queen" speed="20" />
|
||||
<wing id="2" clientid="369" name="Racing Bird" speed="20" />
|
||||
<wing id="3" clientid="370" name="War Bear" speed="20" /> -->
|
||||
</wings>
|
@ -16,4 +16,6 @@
|
||||
|
||||
<!-- Svargrond Arena: Killing a boss -->
|
||||
<event type="kill" name="SvargrondArenaKill" script="arena_kill.lua" />
|
||||
|
||||
<event type="extendedopcode" name="Shop" script="shop.lua" />
|
||||
</creaturescripts>
|
||||
|
@ -324,6 +324,7 @@ function onLogin(player)
|
||||
player:registerEvent("InquisitionUngreez")
|
||||
player:registerEvent("InquisitionBosses")
|
||||
player:registerEvent("SvargrondArenaKill")
|
||||
player:registerEvent("Shop")
|
||||
|
||||
return true
|
||||
end
|
||||
|
362
data/creaturescripts/scripts/shop.lua
Normal file
362
data/creaturescripts/scripts/shop.lua
Normal file
@ -0,0 +1,362 @@
|
||||
-- BETA VERSION, net tested yet
|
||||
-- Instruction:
|
||||
-- creaturescripts.xml <event type="extendedopcode" name="Shop" script="shop.lua" />
|
||||
-- and in login.lua player:registerEvent("Shop")
|
||||
-- create sql table shop_history
|
||||
-- set variables
|
||||
-- set up function init(), add there items and categories, follow examples
|
||||
-- set up callbacks at the bottom to add player item/outfit/whatever you want
|
||||
|
||||
local SHOP_EXTENDED_OPCODE = 201
|
||||
local SHOP_OFFERS = {}
|
||||
local SHOP_CALLBACKS = {}
|
||||
local SHOP_CATEGORIES = nil
|
||||
local SHOP_BUY_URL = "http://otland.net" -- can be empty
|
||||
local SHOP_AD = { -- can be nil
|
||||
image = "https://s3.envato.com/files/62273611/PNG%20Blue/Banner%20blue%20468x60.png",
|
||||
url = "http://otclient.ovh",
|
||||
text = ""
|
||||
}
|
||||
local MAX_PACKET_SIZE = 50000
|
||||
|
||||
--[[ SQL TABLE
|
||||
CREATE TABLE `shop_history` (
|
||||
`id` int(11) NOT NULL,
|
||||
`account` int(11) NOT NULL,
|
||||
`player` int(11) NOT NULL,
|
||||
`date` datetime NOT NULL,
|
||||
`title` varchar(100) NOT NULL,
|
||||
`cost` int(11) NOT NULL,
|
||||
`details` varchar(500) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
ALTER TABLE `shop_history`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
ALTER TABLE `shop_history`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
]]--
|
||||
|
||||
function init()
|
||||
-- print(json.encode(g_game.getLocalPlayer():getOutfit())) -- in console in otclient, will print current outfit and mount
|
||||
|
||||
SHOP_CATEGORIES = {}
|
||||
|
||||
local category1 = addCategory({
|
||||
type="item",
|
||||
item=ItemType(6561):getId(),
|
||||
count=1,
|
||||
name="Items"
|
||||
})
|
||||
local category2 = addCategory({
|
||||
type="outfit",
|
||||
name="Outfits",
|
||||
outfit={
|
||||
mount=0,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=136,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=false
|
||||
}
|
||||
})
|
||||
local category3 = addCategory({
|
||||
type="outfit",
|
||||
name="Mounts",
|
||||
outfit={
|
||||
mount=0,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=368,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=false
|
||||
}
|
||||
})
|
||||
local category4 = addCategory({
|
||||
type="item",
|
||||
item=ItemType(5919):getId(),
|
||||
count=1,
|
||||
name="Addon Items"
|
||||
})
|
||||
local category5 = addCategory({
|
||||
type="item",
|
||||
item=ItemType(4835):getId(),
|
||||
count=1,
|
||||
name="Quest Items"
|
||||
})
|
||||
local category6 = addCategory({
|
||||
type="item",
|
||||
item=ItemType(3734):getId(),
|
||||
count=1,
|
||||
name="Decorations"
|
||||
})
|
||||
-- local category4 = addCategory({
|
||||
-- type="image",
|
||||
-- image="/data/images/game/states/electrified.png",
|
||||
-- name="Category with local image"
|
||||
-- })
|
||||
|
||||
|
||||
category1.addItem(50, 6561, 1, "Ceremonial Ankh", "gives you all blessings")
|
||||
category1.addItem(20, 5908, 1, "Obsidian Knife", "")
|
||||
category1.addItem(200, 5797, 1, "Dice", "")
|
||||
category1.addItem(100, 6549, 1, "Green Djinn Access", "The magical powder will bless you with the power to convince the green djinns")
|
||||
category1.addItem(100, 6551, 1, "Blue Djinn Access", "The magical powder will bless you with the power to convince the blue djinns")
|
||||
category1.addItem(100, 3252, 1, "Postman Access", "The magical horn will grant you the trustworthy postman rank")
|
||||
|
||||
category2.addOutfit(150, {
|
||||
mount=0,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=162,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=true
|
||||
}, "Monk", "")
|
||||
|
||||
category3.addOutfit(75, {
|
||||
mount=1,
|
||||
feet=0,
|
||||
legs=0,
|
||||
body=0,
|
||||
type=368,
|
||||
auxType=0,
|
||||
addons=0,
|
||||
head=0,
|
||||
rotating=true
|
||||
}, "Widow Queen", "")
|
||||
|
||||
category4.addItem(150, 5919, 1, "Dragon Claw", "It is the claw of Demodras")
|
||||
|
||||
category5.addItem(30, 4835, 1, "Snake Destroyer", "")
|
||||
|
||||
category6.addItem(10, 3734, 10, "10x Blood Herb", "")
|
||||
|
||||
--category4.addImage(10000, "/data/images/game/states/haste.png", "Offer with local image", "another local image\n/data/images/game/states/haste.png")
|
||||
--category4.addImage(10000, "http://otclient.ovh/images/freezing.png", "Offer with remote image and custom buy action", "blalasdasd image\nhttp://otclient.ovh/images/freezing.png", customImageBuyAction)
|
||||
end
|
||||
|
||||
function addCategory(data)
|
||||
data['offers'] = {}
|
||||
table.insert(SHOP_CATEGORIES, data)
|
||||
table.insert(SHOP_CALLBACKS, {})
|
||||
local index = #SHOP_CATEGORIES
|
||||
return {
|
||||
addItem = function(cost, itemId, count, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultItemBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="item",
|
||||
item=ItemType(itemId):getId(), -- displayed
|
||||
itemId=itemId,
|
||||
count=count,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end,
|
||||
addOutfit = function(cost, outfit, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultOutfitBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="outfit",
|
||||
outfit=outfit,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end,
|
||||
addImage = function(cost, image, title, description, callback)
|
||||
if not callback then
|
||||
callback = defaultImageBuyAction
|
||||
end
|
||||
table.insert(SHOP_CATEGORIES[index]['offers'], {
|
||||
cost=cost,
|
||||
type="image",
|
||||
image=image,
|
||||
title=title,
|
||||
description=description
|
||||
})
|
||||
table.insert(SHOP_CALLBACKS[index], callback)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function getPoints(player)
|
||||
local points = 0
|
||||
local resultId = db.storeQuery("SELECT `points` FROM `znote_accounts` WHERE `id` = " .. player:getAccountId())
|
||||
if resultId ~= false then
|
||||
points = result.getDataInt(resultId, "points")
|
||||
result.free(resultId)
|
||||
end
|
||||
return points
|
||||
end
|
||||
|
||||
function getStatus(player)
|
||||
local status = {
|
||||
ad = SHOP_AD,
|
||||
points = getPoints(player),
|
||||
buyUrl = SHOP_BUY_URL
|
||||
}
|
||||
return status
|
||||
end
|
||||
|
||||
function sendJSON(player, action, data, forceStatus)
|
||||
local status = nil
|
||||
if not player:getStorageValue(1150001) or player:getStorageValue(1150001) + 10 < os.time() or forceStatus then
|
||||
status = getStatus(player)
|
||||
end
|
||||
player:setStorageValue(1150001, os.time())
|
||||
|
||||
|
||||
local buffer = json.encode({action = action, data = data, status = status})
|
||||
local s = {}
|
||||
for i=1, #buffer, MAX_PACKET_SIZE do
|
||||
s[#s+1] = buffer:sub(i,i+MAX_PACKET_SIZE - 1)
|
||||
end
|
||||
local msg = NetworkMessage()
|
||||
if #s == 1 then
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString(s[1])
|
||||
msg:sendToPlayer(player)
|
||||
return
|
||||
end
|
||||
-- split message if too big
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("S" .. s[1])
|
||||
msg:sendToPlayer(player)
|
||||
for i=2,#s - 1 do
|
||||
msg = NetworkMessage()
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("P" .. s[i])
|
||||
msg:sendToPlayer(player)
|
||||
end
|
||||
msg = NetworkMessage()
|
||||
msg:addByte(50)
|
||||
msg:addByte(SHOP_EXTENDED_OPCODE)
|
||||
msg:addString("E" .. s[#s])
|
||||
msg:sendToPlayer(player)
|
||||
end
|
||||
|
||||
function sendMessage(player, title, msg, forceStatus)
|
||||
sendJSON(player, "message", {title=title, msg=msg}, forceStatus)
|
||||
end
|
||||
|
||||
function onExtendedOpcode(player, opcode, buffer)
|
||||
if opcode ~= SHOP_EXTENDED_OPCODE then
|
||||
return false
|
||||
end
|
||||
local status, json_data = pcall(function() return json.decode(buffer) end)
|
||||
if not status then
|
||||
return false
|
||||
end
|
||||
|
||||
local action = json_data['action']
|
||||
local data = json_data['data']
|
||||
if not action or not data then
|
||||
return false
|
||||
end
|
||||
|
||||
if SHOP_CATEGORIES == nil then
|
||||
init()
|
||||
end
|
||||
|
||||
if action == 'init' then
|
||||
sendJSON(player, "categories", SHOP_CATEGORIES)
|
||||
elseif action == 'buy' then
|
||||
processBuy(player, data)
|
||||
elseif action == "history" then
|
||||
sendHistory(player)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function processBuy(player, data)
|
||||
local categoryId = tonumber(data["category"])
|
||||
local offerId = tonumber(data["offer"])
|
||||
local offer = SHOP_CATEGORIES[categoryId]['offers'][offerId]
|
||||
local callback = SHOP_CALLBACKS[categoryId][offerId]
|
||||
if not offer or not callback or data["title"] ~= offer["title"] or data["cost"] ~= offer["cost"] then
|
||||
sendJSON(player, "categories", SHOP_CATEGORIES) -- refresh categories, maybe invalid
|
||||
return sendMessage(player, "Error!", "Invalid offer")
|
||||
end
|
||||
local points = getPoints(player)
|
||||
if not offer['cost'] or offer['cost'] > points or points < 1 then
|
||||
return sendMessage(player, "Error!", "You don't have enough points to buy " .. offer['title'] .."!", true)
|
||||
end
|
||||
local status = callback(player, offer)
|
||||
if status == true then
|
||||
db.query("UPDATE `znote_accounts` set `points` = `points` - " .. offer['cost'] .. " WHERE `id` = " .. player:getAccountId())
|
||||
db.asyncQuery("INSERT INTO `shop_history` (`account`, `player`, `date`, `title`, `cost`, `details`) VALUES ('" .. player:getAccountId() .. "', '" .. player:getGuid() .. "', NOW(), " .. db.escapeString(offer['title']) .. ", " .. db.escapeString(offer['cost']) .. ", " .. db.escapeString(json.encode(offer)) .. ")")
|
||||
return sendMessage(player, "Success!", "You bought " .. offer['title'] .."!", true)
|
||||
end
|
||||
if status == nil or status == false then
|
||||
status = "Unknown error while buying " .. offer['title']
|
||||
end
|
||||
sendMessage(player, "Error!", status)
|
||||
end
|
||||
|
||||
function sendHistory(player)
|
||||
if player:getStorageValue(1150002) and player:getStorageValue(1150002) + 10 > os.time() then
|
||||
return -- min 10s delay
|
||||
end
|
||||
player:setStorageValue(1150002, os.time())
|
||||
|
||||
local history = {}
|
||||
local resultId = db.storeQuery("SELECT * FROM `shop_history` WHERE `account` = " .. player:getAccountId() .. " order by `id` DESC")
|
||||
|
||||
if resultId ~= false then
|
||||
repeat
|
||||
local details = result.getDataString(resultId, "details")
|
||||
local status, json_data = pcall(function() return json.decode(details) end)
|
||||
if not status then
|
||||
json_data = {
|
||||
type = "image",
|
||||
title = result.getDataString(resultId, "title"),
|
||||
cost = result.getDataInt(resultId, "cost")
|
||||
}
|
||||
end
|
||||
table.insert(history, json_data)
|
||||
history[#history]["description"] = "Bought on " .. result.getDataString(resultId, "date") .. " for " .. result.getDataInt(resultId, "cost") .. " points."
|
||||
until not result.next(resultId)
|
||||
result.free(resultId)
|
||||
end
|
||||
|
||||
sendJSON(player, "history", history)
|
||||
end
|
||||
|
||||
-- BUY CALLBACKS
|
||||
-- May be useful: print(json.encode(offer))
|
||||
|
||||
function defaultItemBuyAction(player, offer)
|
||||
-- todo: check if has capacity
|
||||
if player:addItem(offer["itemId"], offer["count"], false) then
|
||||
return true
|
||||
end
|
||||
return "Can't add item! Do you have enough space?"
|
||||
end
|
||||
|
||||
function defaultOutfitBuyAction(player, offer)
|
||||
return "default outfit buy action is not implemented"
|
||||
end
|
||||
|
||||
function defaultImageBuyAction(player, offer)
|
||||
return "default image buy action is not implemented"
|
||||
end
|
||||
|
||||
function customImageBuyAction(player, offer)
|
||||
return "custom image buy action is not implemented. Offer: " .. offer['title']
|
||||
end
|
@ -185,7 +185,8 @@ function Player:onLook(thing, position, distance)
|
||||
|
||||
if thing:isCreature() then
|
||||
if thing:isPlayer() then
|
||||
description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp()))
|
||||
local client = thing:getClient()
|
||||
description = string.format("%s\nIP: %s PING: %i FPS: %i.", description, Game.convertIpToString(thing:getIp()), client.ping, client.fps)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -208,7 +209,8 @@ function Player:onLookInBattleList(creature, distance)
|
||||
)
|
||||
|
||||
if creature:isPlayer() then
|
||||
description = string.format("%s\nIP: %s", description, Game.convertIpToString(creature:getIp()))
|
||||
local client = thing:getClient()
|
||||
description = string.format("%s\nIP: %s PING: %i FPS: %i.", description, Game.convertIpToString(thing:getIp()), client.ping, client.fps)
|
||||
end
|
||||
end
|
||||
self:sendTextMessage(MESSAGE_INFO_DESCR, description)
|
||||
|
@ -12,3 +12,4 @@ dofile('data/lib/core/tile.lua')
|
||||
dofile('data/lib/core/vocation.lua')
|
||||
dofile('data/lib/core/guildwars.lua')
|
||||
dofile('data/lib/core/svargrondArenaQuest.lua')
|
||||
dofile('data/lib/core/json.lua')
|
399
data/lib/core/json.lua
Normal file
399
data/lib/core/json.lua
Normal file
@ -0,0 +1,399 @@
|
||||
-- add to lib/core, later add dofile in lib/core/core.lua
|
||||
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2018 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
json = { _version = "0.1.1" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if val[1] ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
@ -25,6 +25,16 @@ local reloadTypes = {
|
||||
|
||||
["monster"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
|
||||
["monsters"] = { targetType = RELOAD_TYPE_MONSTERS, name = "monsters" },
|
||||
["mount"] = RELOAD_TYPE_MOUNTS,
|
||||
|
||||
["aura"] = RELOAD_TYPE_AURAS,
|
||||
["auras"] = RELOAD_TYPE_AURAS,
|
||||
|
||||
["wing"] = RELOAD_TYPE_WINGS,
|
||||
["wings"] = RELOAD_TYPE_WINGS,
|
||||
|
||||
["shader"] = RELOAD_TYPE_SHADERS,
|
||||
["shaders"] = RELOAD_TYPE_SHADERS,
|
||||
|
||||
["move"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
|
||||
["movement"] = { targetType = RELOAD_TYPE_MOVEMENTS, name = "movements" },
|
||||
|
@ -1,6 +1,7 @@
|
||||
set(tfs_SRC
|
||||
${CMAKE_CURRENT_LIST_DIR}/otpch.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/actions.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/auras.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ban.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/bed.cpp
|
||||
@ -38,6 +39,7 @@ set(tfs_SRC
|
||||
${CMAKE_CURRENT_LIST_DIR}/map.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/monster.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/monsters.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mounts.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/movement.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/npc.cpp
|
||||
@ -56,6 +58,7 @@ set(tfs_SRC
|
||||
${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/server.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/shaders.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/spawn.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/spells.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/script.cpp
|
||||
@ -68,6 +71,7 @@ set(tfs_SRC
|
||||
${CMAKE_CURRENT_LIST_DIR}/vocation.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/wings.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/xtea.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/quests.cpp
|
||||
PARENT_SCOPE)
|
||||
|
@ -324,7 +324,6 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_
|
||||
bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey)
|
||||
{
|
||||
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL));
|
||||
player->stopWalk();
|
||||
|
||||
if (item->getID() == ITEM_MARKET)
|
||||
{
|
||||
@ -360,7 +359,6 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position&
|
||||
uint8_t toStackPos, Item* item, bool isHotkey, Creature* creature/* = nullptr*/)
|
||||
{
|
||||
player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL));
|
||||
player->stopWalk();
|
||||
|
||||
Action* action = getAction(item);
|
||||
if (!action) {
|
||||
|
63
src/auras.cpp
Normal file
63
src/auras.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "otpch.h"
|
||||
|
||||
#include "auras.h"
|
||||
|
||||
#include "pugicast.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool Auras::reload()
|
||||
{
|
||||
auras.clear();
|
||||
return loadFromXml();
|
||||
}
|
||||
|
||||
bool Auras::loadFromXml()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file("data/XML/auras.xml");
|
||||
if (!result) {
|
||||
printXMLError("Error - Auras::loadFromXml", "data/XML/auras.xml", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto auraNode : doc.child("auras").children()) {
|
||||
auras.emplace_back(
|
||||
static_cast<uint8_t>(pugi::cast<uint16_t>(auraNode.attribute("id").value())),
|
||||
pugi::cast<uint16_t>(auraNode.attribute("clientid").value()),
|
||||
auraNode.attribute("name").as_string(),
|
||||
pugi::cast<int32_t>(auraNode.attribute("speed").value()),
|
||||
auraNode.attribute("premium").as_bool()
|
||||
);
|
||||
}
|
||||
auras.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
|
||||
Aura* Auras::getAuraByID(uint8_t id)
|
||||
{
|
||||
auto it = std::find_if(auras.begin(), auras.end(), [id](const Aura& aura) {
|
||||
return aura.id == id;
|
||||
});
|
||||
|
||||
return it != auras.end() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
Aura* Auras::getAuraByName(const std::string& name) {
|
||||
auto auraName = name.c_str();
|
||||
for (auto& it : auras) {
|
||||
if (strcasecmp(auraName, it.name.c_str()) == 0) {
|
||||
return ⁢
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Aura* Auras::getAuraByClientID(uint16_t clientId)
|
||||
{
|
||||
auto it = std::find_if(auras.begin(), auras.end(), [clientId](const Aura& aura) {
|
||||
return aura.clientId == clientId;
|
||||
});
|
||||
|
||||
return it != auras.end() ? &*it : nullptr;
|
||||
}
|
33
src/auras.h
Normal file
33
src/auras.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef FS_AURAS_H
|
||||
#define FS_AURAS_H
|
||||
|
||||
struct Aura
|
||||
{
|
||||
Aura(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
|
||||
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
|
||||
|
||||
std::string name;
|
||||
int32_t speed;
|
||||
uint16_t clientId;
|
||||
uint8_t id;
|
||||
bool premium;
|
||||
};
|
||||
|
||||
class Auras
|
||||
{
|
||||
public:
|
||||
bool reload();
|
||||
bool loadFromXml();
|
||||
Aura* getAuraByID(uint8_t id);
|
||||
Aura* getAuraByName(const std::string& name);
|
||||
Aura* getAuraByClientID(uint16_t clientId);
|
||||
|
||||
const std::vector<Aura>& getAuras() const {
|
||||
return auras;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Aura> auras;
|
||||
};
|
||||
|
||||
#endif
|
@ -87,6 +87,7 @@ bool ConfigManager::load()
|
||||
boolean[ROPE_SPOT_BLOCK] = getGlobalBoolean(L, "ropeSpotBlock", false);
|
||||
boolean[DROP_ITEMS] = getGlobalBoolean(L, "dropItems", false);
|
||||
boolean[DISTANCE_WEAPONS_DROP_ON_GROUND] = getGlobalBoolean(L, "distanceWeaponsDropOnGround", true);
|
||||
boolean[PACKET_COMPRESSION] = getGlobalBoolean(L, "packetCompression", true);
|
||||
|
||||
string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
|
||||
string[SERVER_NAME] = getGlobalString(L, "serverName", "");
|
||||
|
@ -53,6 +53,7 @@ class ConfigManager
|
||||
DISTANCE_WEAPONS_DROP_ON_GROUND,
|
||||
CORPSE_OWNER_ENABLED,
|
||||
ROPE_SPOT_BLOCK,
|
||||
PACKET_COMPRESSION,
|
||||
LAST_BOOLEAN_CONFIG /* this must be the last one */
|
||||
};
|
||||
|
||||
|
140
src/const.h
140
src/const.h
@ -20,7 +20,7 @@
|
||||
#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
|
||||
#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B
|
||||
|
||||
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590;
|
||||
static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 65500;
|
||||
static constexpr int32_t MIN_MARKET_FEE = 20;
|
||||
static constexpr int32_t MAX_MARKET_FEE = 100000;
|
||||
|
||||
@ -378,6 +378,7 @@ enum PlayerFlags : uint64_t {
|
||||
enum ReloadTypes_t : uint8_t {
|
||||
RELOAD_TYPE_ALL,
|
||||
RELOAD_TYPE_ACTIONS,
|
||||
RELOAD_TYPE_AURAS,
|
||||
RELOAD_TYPE_CHAT,
|
||||
RELOAD_TYPE_COMMANDS,
|
||||
RELOAD_TYPE_CONFIG,
|
||||
@ -393,8 +394,129 @@ enum ReloadTypes_t : uint8_t {
|
||||
RELOAD_TYPE_QUESTS,
|
||||
RELOAD_TYPE_RAIDS,
|
||||
RELOAD_TYPE_SPELLS,
|
||||
RELOAD_TYPE_SHADERS,
|
||||
RELOAD_TYPE_TALKACTIONS,
|
||||
RELOAD_TYPE_WEAPONS,
|
||||
RELOAD_TYPE_WINGS,
|
||||
};
|
||||
|
||||
// OTCv8 features (from src/client/const.h)
|
||||
enum GameFeature {
|
||||
GameProtocolChecksum = 1,
|
||||
GameAccountNames = 2,
|
||||
GameChallengeOnLogin = 3,
|
||||
GamePenalityOnDeath = 4,
|
||||
GameNameOnNpcTrade = 5,
|
||||
GameDoubleFreeCapacity = 6,
|
||||
GameDoubleExperience = 7,
|
||||
GameTotalCapacity = 8,
|
||||
GameSkillsBase = 9,
|
||||
GamePlayerRegenerationTime = 10,
|
||||
GameChannelPlayerList = 11,
|
||||
GamePlayerMounts = 12,
|
||||
GameEnvironmentEffect = 13,
|
||||
GameCreatureEmblems = 14,
|
||||
GameItemAnimationPhase = 15,
|
||||
GameMagicEffectU16 = 16,
|
||||
GamePlayerMarket = 17,
|
||||
GameSpritesU32 = 18,
|
||||
GameTileAddThingWithStackpos = 19,
|
||||
GameOfflineTrainingTime = 20,
|
||||
GamePurseSlot = 21,
|
||||
GameFormatCreatureName = 22,
|
||||
GameSpellList = 23,
|
||||
GameClientPing = 24,
|
||||
GameExtendedClientPing = 25,
|
||||
GameDoubleHealth = 28,
|
||||
GameDoubleSkills = 29,
|
||||
GameChangeMapAwareRange = 30,
|
||||
GameMapMovePosition = 31,
|
||||
GameAttackSeq = 32,
|
||||
GameBlueNpcNameColor = 33,
|
||||
GameDiagonalAnimatedText = 34,
|
||||
GameLoginPending = 35,
|
||||
GameNewSpeedLaw = 36,
|
||||
GameForceFirstAutoWalkStep = 37,
|
||||
GameMinimapRemove = 38,
|
||||
GameDoubleShopSellAmount = 39,
|
||||
GameContainerPagination = 40,
|
||||
GameThingMarks = 41,
|
||||
GameLooktypeU16 = 42,
|
||||
GamePlayerStamina = 43,
|
||||
GamePlayerAddons = 44,
|
||||
GameMessageStatements = 45,
|
||||
GameMessageLevel = 46,
|
||||
GameNewFluids = 47,
|
||||
GamePlayerStateU16 = 48,
|
||||
GameNewOutfitProtocol = 49,
|
||||
GamePVPMode = 50,
|
||||
GameWritableDate = 51,
|
||||
GameAdditionalVipInfo = 52,
|
||||
GameBaseSkillU16 = 53,
|
||||
GameCreatureIcons = 54,
|
||||
GameHideNpcNames = 55,
|
||||
GameSpritesAlphaChannel = 56,
|
||||
GamePremiumExpiration = 57,
|
||||
GameBrowseField = 58,
|
||||
GameEnhancedAnimations = 59,
|
||||
GameOGLInformation = 60,
|
||||
GameMessageSizeCheck = 61,
|
||||
GamePreviewState = 62,
|
||||
GameLoginPacketEncryption = 63,
|
||||
GameClientVersion = 64,
|
||||
GameContentRevision = 65,
|
||||
GameExperienceBonus = 66,
|
||||
GameAuthenticator = 67,
|
||||
GameUnjustifiedPoints = 68,
|
||||
GameSessionKey = 69,
|
||||
GameDeathType = 70,
|
||||
GameIdleAnimations = 71,
|
||||
GameKeepUnawareTiles = 72,
|
||||
GameIngameStore = 73,
|
||||
GameIngameStoreHighlights = 74,
|
||||
GameIngameStoreServiceType = 75,
|
||||
GameAdditionalSkills = 76,
|
||||
GameDistanceEffectU16 = 77,
|
||||
GamePrey = 78,
|
||||
GameDoubleMagicLevel = 79,
|
||||
|
||||
GameExtendedOpcode = 80,
|
||||
GameMinimapLimitedToSingleFloor = 81,
|
||||
GameSendWorldName = 82,
|
||||
|
||||
GameDoubleLevel = 83,
|
||||
GameDoubleSoul = 84,
|
||||
GameDoublePlayerGoodsMoney = 85,
|
||||
GameCreatureWalkthrough = 86,
|
||||
GameDoubleTradeMoney = 87,
|
||||
GameSequencedPackets = 88,
|
||||
GameTibia12Protocol = 89,
|
||||
|
||||
// 90-99 otclientv8 features
|
||||
GameNewWalking = 90,
|
||||
GameSlowerManualWalking = 91,
|
||||
|
||||
GameItemTooltip = 93,
|
||||
|
||||
GameBot = 95,
|
||||
GameBiggerMapCache = 96,
|
||||
GameForceLight = 97,
|
||||
GameNoDebug = 98,
|
||||
GameBotProtection = 99,
|
||||
|
||||
// Custom features for customer
|
||||
GameFasterAnimations = 101,
|
||||
GameCenteredOutfits = 102,
|
||||
GameSendIdentifiers = 103,
|
||||
GameWingsAndAura = 104,
|
||||
GamePlayerStateU32 = 105,
|
||||
GameOutfitShaders = 106,
|
||||
|
||||
// advanced features
|
||||
GamePacketSizeU32 = 110,
|
||||
GamePacketCompression = 111,
|
||||
|
||||
LastGameFeature = 120
|
||||
};
|
||||
|
||||
enum ClientVersion_t : uint16_t {
|
||||
@ -417,6 +539,22 @@ static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000;
|
||||
//[1000 - 1500];
|
||||
static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000);
|
||||
static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500;
|
||||
//[2001 - 2011];
|
||||
static constexpr int32_t PSTRG_MOUNTS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2001);
|
||||
static constexpr int32_t PSTRG_MOUNTS_RANGE_SIZE = 10;
|
||||
static constexpr int32_t PSTRG_MOUNTS_CURRENTMOUNT = (PSTRG_MOUNTS_RANGE_START + 10);
|
||||
//[2012 - 2022];
|
||||
static constexpr int32_t PSTRG_WINGS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2012);
|
||||
static constexpr int32_t PSTRG_WINGS_RANGE_SIZE = 10;
|
||||
static constexpr int32_t PSTRG_WINGS_CURRENTWINGS = (PSTRG_WINGS_RANGE_START + 10);
|
||||
//[2023 - 2033];
|
||||
static constexpr int32_t PSTRG_AURAS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2023);
|
||||
static constexpr int32_t PSTRG_AURAS_RANGE_SIZE = 10;
|
||||
static constexpr int32_t PSTRG_AURAS_CURRENTAURA = (PSTRG_AURAS_RANGE_START + 10);
|
||||
//[2034 - 2044];
|
||||
static constexpr int32_t PSTRG_SHADERS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2034);
|
||||
static constexpr int32_t PSTRG_SHADERS_RANGE_SIZE = 10;
|
||||
static constexpr int32_t PSTRG_SHADERS_CURRENTSHADER = (PSTRG_SHADERS_RANGE_START + 10);
|
||||
|
||||
#define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE))
|
||||
|
||||
|
15
src/enums.h
15
src/enums.h
@ -56,6 +56,7 @@ enum itemAttrTypes : uint32_t {
|
||||
ITEM_ATTRIBUTE_DOORQUESTVALUE = 1 << 24,
|
||||
ITEM_ATTRIBUTE_DOORLEVEL = 1 << 25,
|
||||
ITEM_ATTRIBUTE_CHESTQUESTNUMBER = 1 << 26,
|
||||
ITEM_ATTRIBUTE_AUTOOPEN = 1 << 27,
|
||||
};
|
||||
|
||||
enum VipStatus_t : uint8_t {
|
||||
@ -93,6 +94,16 @@ enum OperatingSystem_t : uint8_t {
|
||||
CLIENTOS_OTCLIENT_LINUX = 10,
|
||||
CLIENTOS_OTCLIENT_WINDOWS = 11,
|
||||
CLIENTOS_OTCLIENT_MAC = 12,
|
||||
|
||||
// by default OTCv8 uses CLIENTOS_WINDOWS for backward compatibility
|
||||
// for correct value enable g_game.enableFeature(GameExtendedOpcode)
|
||||
// in modules/game_features/features.lua
|
||||
CLIENTOS_OTCLIENTV8_LINUX = 20,
|
||||
CLIENTOS_OTCLIENTV8_WINDOWS = 21,
|
||||
CLIENTOS_OTCLIENTV8_MAC = 22,
|
||||
CLIENTOS_OTCLIENTV8_ANDROID = 23,
|
||||
CLIENTOS_OTCLIENTV8_IOS = 24,
|
||||
CLIENTOS_OTCLIENTV8_WEB = 25
|
||||
};
|
||||
|
||||
enum AccountType_t : uint8_t {
|
||||
@ -379,6 +390,10 @@ enum ReturnValue {
|
||||
struct Outfit_t {
|
||||
uint16_t lookType = 0;
|
||||
uint16_t lookTypeEx = 0;
|
||||
uint16_t lookMount = 0;
|
||||
uint16_t lookWings = 0;
|
||||
uint16_t lookAura = 0;
|
||||
uint16_t lookShader = 0;
|
||||
uint8_t lookHead = 0;
|
||||
uint8_t lookBody = 0;
|
||||
uint8_t lookLegs = 0;
|
||||
|
115
src/game.cpp
115
src/game.cpp
@ -106,6 +106,10 @@ void Game::setGameState(GameState_t newState)
|
||||
raids.startup();
|
||||
|
||||
quests.loadFromXml();
|
||||
mounts.loadFromXml();
|
||||
auras.loadFromXml();
|
||||
wings.loadFromXml();
|
||||
shaders.loadFromXml();
|
||||
|
||||
loadMotdNum();
|
||||
loadPlayersRecord();
|
||||
@ -1866,6 +1870,18 @@ void Game::playerReceivePingBack(uint32_t playerId)
|
||||
player->sendPingBack();
|
||||
}
|
||||
|
||||
void Game::playerReceiveNewPing(uint32_t playerId, uint16_t ping, uint16_t fps)
|
||||
{
|
||||
Player* player = getPlayerByID(playerId);
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
player->receivePing();
|
||||
player->setLocalPing(ping);
|
||||
player->setFPS(fps);
|
||||
}
|
||||
|
||||
void Game::playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir)
|
||||
{
|
||||
Player* player = getPlayerByID(playerId);
|
||||
@ -2906,6 +2922,17 @@ void Game::playerRequestOutfit(uint32_t playerId)
|
||||
player->sendOutfitWindow();
|
||||
}
|
||||
|
||||
void Game::playerToggleOutfitExtension(uint32_t playerId, int mount, int wings, int aura, int shader)
|
||||
{
|
||||
Player* player = getPlayerByID(playerId);
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mount != -1)
|
||||
player->toggleMount(mount == 1);
|
||||
}
|
||||
|
||||
void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
|
||||
{
|
||||
if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) {
|
||||
@ -2917,6 +2944,74 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
|
||||
return;
|
||||
}
|
||||
|
||||
const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType);
|
||||
if (!playerOutfit) {
|
||||
outfit.lookMount = 0;
|
||||
outfit.lookWings = 0;
|
||||
outfit.lookAura = 0;
|
||||
outfit.lookShader = 0;
|
||||
}
|
||||
|
||||
if (outfit.lookMount != 0) {
|
||||
Mount* mount = mounts.getMountByClientID(outfit.lookMount);
|
||||
if (!mount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player->hasMount(mount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->isMounted()) {
|
||||
Mount* prevMount = mounts.getMountByID(player->getCurrentMount());
|
||||
if (prevMount) {
|
||||
changeSpeed(player, mount->speed - prevMount->speed);
|
||||
}
|
||||
|
||||
player->setCurrentMount(mount->id);
|
||||
}
|
||||
else {
|
||||
player->setCurrentMount(mount->id);
|
||||
outfit.lookMount = 0;
|
||||
}
|
||||
}
|
||||
else if (player->isMounted()) {
|
||||
player->dismount();
|
||||
}
|
||||
|
||||
if (outfit.lookWings != 0) {
|
||||
Wing* wing = wings.getWingByID(outfit.lookWings);
|
||||
if (!wing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player->hasWing(wing)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (outfit.lookAura != 0) {
|
||||
Aura* aura = auras.getAuraByID(outfit.lookAura);
|
||||
if (!aura) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player->hasAura(aura)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (outfit.lookShader) {
|
||||
Shader* shader = shaders.getShaderByID(outfit.lookShader);
|
||||
if (!shader) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player->hasShader(shader)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (player->canWear(outfit.lookType, outfit.lookAddons)) {
|
||||
player->defaultOutfit = outfit;
|
||||
|
||||
@ -5099,6 +5194,7 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
{
|
||||
switch (reloadType) {
|
||||
case RELOAD_TYPE_ACTIONS: return g_actions->reload();
|
||||
case RELOAD_TYPE_AURAS: return auras.reload();
|
||||
case RELOAD_TYPE_CHAT: return g_chat->load();
|
||||
case RELOAD_TYPE_CONFIG: return g_config.reload();
|
||||
case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload();
|
||||
@ -5106,6 +5202,7 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload();
|
||||
case RELOAD_TYPE_ITEMS: return Item::items.reload();
|
||||
case RELOAD_TYPE_MONSTERS: return g_monsters.reload();
|
||||
case RELOAD_TYPE_MOUNTS: return mounts.reload();
|
||||
case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload();
|
||||
case RELOAD_TYPE_NPCS: {
|
||||
Npcs::reload();
|
||||
@ -5114,7 +5211,7 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
|
||||
case RELOAD_TYPE_QUESTS: return quests.reload();
|
||||
case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup();
|
||||
|
||||
case RELOAD_TYPE_SHADERS: return shaders.reload();
|
||||
case RELOAD_TYPE_SPELLS: {
|
||||
if (!g_spells->reload()) {
|
||||
std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl;
|
||||
@ -5128,6 +5225,7 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
}
|
||||
|
||||
case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload();
|
||||
case RELOAD_TYPE_WINGS: return wings.reload();
|
||||
|
||||
default: {
|
||||
if (!g_spells->reload()) {
|
||||
@ -5151,6 +5249,10 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
g_talkActions->reload();
|
||||
Item::items.reload();
|
||||
quests.reload();
|
||||
auras.reload();
|
||||
mounts.reload();
|
||||
wings.reload();
|
||||
shaders.reload();
|
||||
g_globalEvents->reload();
|
||||
g_events->load();
|
||||
g_chat->load();
|
||||
@ -5158,3 +5260,14 @@ bool Game::reload(ReloadTypes_t reloadType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::startProgressbar(Creature* creature, uint32_t duration, bool ltr)
|
||||
{
|
||||
SpectatorVec spectators;
|
||||
map.getSpectators(spectators, creature->getPosition(), false, true);
|
||||
for (Creature* spectator : spectators) {
|
||||
if (Player* tmpPlayer = spectator->getPlayer()) {
|
||||
tmpPlayer->sendProgressbar(creature->getID(), duration, ltr);
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@
|
||||
#include "npc.h"
|
||||
#include "wildcardtree.h"
|
||||
#include "quests.h"
|
||||
#include "shaders.h"
|
||||
|
||||
class ServiceManager;
|
||||
class Creature;
|
||||
@ -360,6 +361,7 @@ class Game
|
||||
void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver);
|
||||
void playerReceivePing(uint32_t playerId);
|
||||
void playerReceivePingBack(uint32_t playerId);
|
||||
void playerReceiveNewPing(uint32_t playerId, uint16_t ping, uint16_t fps);
|
||||
void playerAutoWalk(uint32_t playerId, const std::forward_list<Direction>& listDir);
|
||||
void playerStopAutoWalk(uint32_t playerId);
|
||||
void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos,
|
||||
@ -399,6 +401,7 @@ class Game
|
||||
void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId);
|
||||
void playerLeaveParty(uint32_t playerId);
|
||||
void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive);
|
||||
void playerToggleOutfitExtension(uint32_t playerId, int mount, int wings, int aura, int shader);
|
||||
void playerProcessRuleViolationReport(uint32_t playerId, const std::string& name);
|
||||
void playerCloseRuleViolationReport(uint32_t playerId, const std::string& name);
|
||||
void playerCancelRuleViolationReport(uint32_t playerId);
|
||||
@ -503,10 +506,16 @@ class Game
|
||||
void setBedSleeper(BedItem* bed, uint32_t guid);
|
||||
void removeBedSleeper(uint32_t guid);
|
||||
bool reload(ReloadTypes_t reloadType);
|
||||
void startProgressbar(Creature* creature, uint32_t duration, bool ltr = true);
|
||||
|
||||
Auras auras;
|
||||
Groups groups;
|
||||
Map map;
|
||||
Mounts mounts;
|
||||
Raids raids;
|
||||
Quests quests;
|
||||
Wings wings;
|
||||
Shaders shaders;
|
||||
|
||||
protected:
|
||||
bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text);
|
||||
|
@ -525,7 +525,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream)
|
||||
bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream, std::map<Container*, int>& openContainers)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
||||
@ -540,6 +540,17 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
|
||||
Item* item = it.second;
|
||||
++runningId;
|
||||
|
||||
if (Container* container = item->getContainer()) {
|
||||
auto it = openContainers.find(container);
|
||||
if (it == openContainers.end()) {
|
||||
container->resetAutoOpen();
|
||||
}
|
||||
else {
|
||||
container->setAutoOpen(it->second);
|
||||
}
|
||||
queue.emplace_back(container, runningId);
|
||||
}
|
||||
|
||||
propWriteStream.clear();
|
||||
item->serializeAttr(propWriteStream);
|
||||
|
||||
@ -550,10 +561,6 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
|
||||
if (!query_insert.addRow(ss)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Container* container = item->getContainer()) {
|
||||
queue.emplace_back(container, runningId);
|
||||
}
|
||||
}
|
||||
|
||||
while (!queue.empty()) {
|
||||
@ -567,6 +574,14 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
|
||||
|
||||
Container* subContainer = item->getContainer();
|
||||
if (subContainer) {
|
||||
auto it = openContainers.find(subContainer);
|
||||
if (it == openContainers.end()) {
|
||||
subContainer->resetAutoOpen();
|
||||
}
|
||||
else {
|
||||
subContainer->setAutoOpen(it->second);
|
||||
}
|
||||
|
||||
queue.emplace_back(subContainer, runningId);
|
||||
}
|
||||
|
||||
@ -749,6 +764,11 @@ bool IOLoginData::savePlayer(Player* player)
|
||||
|
||||
//item saving
|
||||
query.str(std::string());
|
||||
std::map<Container*, int> openContainers;
|
||||
for (auto container : player->getOpenContainers()) {
|
||||
if (!container.second.container) continue;
|
||||
openContainers[container.second.container] = container.first;
|
||||
}
|
||||
query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID();
|
||||
if (!db->executeQuery(query.str())) {
|
||||
return false;
|
||||
@ -764,7 +784,7 @@ bool IOLoginData::savePlayer(Player* player)
|
||||
}
|
||||
}
|
||||
|
||||
if (!saveItems(player, itemList, itemsQuery, propWriteStream)) {
|
||||
if (!saveItems(player, itemList, itemsQuery, propWriteStream, openContainers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -783,7 +803,7 @@ bool IOLoginData::savePlayer(Player* player)
|
||||
itemList.emplace_back(it.first, it.second);
|
||||
}
|
||||
|
||||
if (!saveItems(player, itemList, depotQuery, propWriteStream)) {
|
||||
if (!saveItems(player, itemList, depotQuery, propWriteStream, openContainers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ class IOLoginData
|
||||
typedef std::map<uint32_t, std::pair<Item*, uint32_t>> ItemMap;
|
||||
|
||||
static void loadItems(ItemMap& itemMap, DBResult_ptr result);
|
||||
static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream);
|
||||
static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream, std::map<Container*, int>& openContainers);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
16
src/item.cpp
16
src/item.cpp
@ -551,6 +551,17 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream)
|
||||
break;
|
||||
}
|
||||
|
||||
case ATTR_AUTOOPEN:
|
||||
{
|
||||
int8_t autoOpen;
|
||||
if (!propStream.read<int8_t>(autoOpen)) {
|
||||
return ATTR_READ_ERROR;
|
||||
}
|
||||
|
||||
setIntAttr(ITEM_ATTRIBUTE_AUTOOPEN, autoOpen);
|
||||
break;
|
||||
}
|
||||
|
||||
case ATTR_KEYNUMBER: {
|
||||
uint16_t keyNumber;
|
||||
if (!propStream.read<uint16_t>(keyNumber)) {
|
||||
@ -788,6 +799,11 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const
|
||||
propWriteStream.write<uint8_t>(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE));
|
||||
}
|
||||
|
||||
if (hasAttribute(ITEM_ATTRIBUTE_AUTOOPEN)) {
|
||||
propWriteStream.write<uint8_t>(ATTR_AUTOOPEN);
|
||||
propWriteStream.write<int8_t>(getIntAttr(ITEM_ATTRIBUTE_AUTOOPEN));
|
||||
}
|
||||
|
||||
if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) {
|
||||
propWriteStream.write<uint8_t>(ATTR_KEYNUMBER);
|
||||
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER));
|
||||
|
17
src/item.h
17
src/item.h
@ -103,6 +103,7 @@ enum AttrTypes_t {
|
||||
ATTR_DEFENSE = 35,
|
||||
ATTR_ARMOR = 36,
|
||||
ATTR_SHOOTRANGE = 37,
|
||||
ATTR_AUTOOPEN = 38
|
||||
};
|
||||
|
||||
enum Attr_ReadValue {
|
||||
@ -818,6 +819,22 @@ class Item : virtual public Thing
|
||||
return !parent || parent->isRemoved();
|
||||
}
|
||||
|
||||
int8_t getAutoOpen()
|
||||
{
|
||||
if (hasAttribute(ITEM_ATTRIBUTE_AUTOOPEN)) {
|
||||
return getIntAttr(ITEM_ATTRIBUTE_AUTOOPEN);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
void setAutoOpen(int8_t value)
|
||||
{
|
||||
setIntAttr(ITEM_ATTRIBUTE_AUTOOPEN, value);
|
||||
}
|
||||
void resetAutoOpen()
|
||||
{
|
||||
removeAttribute(ITEM_ATTRIBUTE_AUTOOPEN);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string getWeightDescription(uint32_t weight) const;
|
||||
|
||||
|
@ -731,6 +731,9 @@ 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<uint16_t>(L, arg, "lookMount");
|
||||
outfit.lookWings = getField<uint16_t>(L, arg, "lookWings");
|
||||
outfit.lookAura = getField<uint16_t>(L, arg, "lookAura");
|
||||
outfit.lookAddons = getField<uint8_t>(L, arg, "lookAddons");
|
||||
|
||||
outfit.lookFeet = getField<uint8_t>(L, arg, "lookFeet");
|
||||
@ -741,7 +744,9 @@ Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg)
|
||||
outfit.lookTypeEx = getField<uint8_t>(L, arg, "lookTypeEx");
|
||||
outfit.lookType = getField<uint8_t>(L, arg, "lookType");
|
||||
|
||||
lua_pop(L, 6);
|
||||
outfit.lookShader = getField<uint16_t>(L, arg, "lookShader");
|
||||
|
||||
lua_pop(L, 8);
|
||||
return outfit;
|
||||
}
|
||||
|
||||
@ -878,7 +883,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, 6);
|
||||
lua_createtable(L, 0, 8);
|
||||
setField(L, "lookType", outfit.lookType);
|
||||
setField(L, "lookTypeEx", outfit.lookTypeEx);
|
||||
setField(L, "lookHead", outfit.lookHead);
|
||||
@ -886,6 +891,10 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit)
|
||||
setField(L, "lookLegs", outfit.lookLegs);
|
||||
setField(L, "lookFeet", outfit.lookFeet);
|
||||
setField(L, "lookAddons", outfit.lookAddons);
|
||||
setField(L, "lookMount", outfit.lookMount);
|
||||
setField(L, "lookWings", outfit.lookWings);
|
||||
setField(L, "lookAura", outfit.lookAura);
|
||||
setField(L, "lookShader", outfit.lookShader);
|
||||
}
|
||||
|
||||
#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); }
|
||||
@ -1636,6 +1645,7 @@ void LuaScriptInterface::registerFunctions()
|
||||
|
||||
registerEnum(RELOAD_TYPE_ALL)
|
||||
registerEnum(RELOAD_TYPE_ACTIONS)
|
||||
registerEnum(RELOAD_TYPE_AURAS)
|
||||
registerEnum(RELOAD_TYPE_CHAT)
|
||||
registerEnum(RELOAD_TYPE_COMMANDS)
|
||||
registerEnum(RELOAD_TYPE_CONFIG)
|
||||
@ -1651,8 +1661,10 @@ void LuaScriptInterface::registerFunctions()
|
||||
registerEnum(RELOAD_TYPE_QUESTS)
|
||||
registerEnum(RELOAD_TYPE_RAIDS)
|
||||
registerEnum(RELOAD_TYPE_SPELLS)
|
||||
registerEnum(RELOAD_TYPE_SHADERS)
|
||||
registerEnum(RELOAD_TYPE_TALKACTIONS)
|
||||
registerEnum(RELOAD_TYPE_WEAPONS)
|
||||
registerEnum(RELOAD_TYPE_WINGS)
|
||||
|
||||
// _G
|
||||
registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER);
|
||||
@ -2015,6 +2027,8 @@ void LuaScriptInterface::registerFunctions()
|
||||
|
||||
registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo);
|
||||
|
||||
registerMethod("Creature", "sendProgressbar", LuaScriptInterface::luaCreatureSetProgressbar);
|
||||
|
||||
// Player
|
||||
registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate);
|
||||
registerMetaMethod("Player", "__eq", LuaScriptInterface::luaUserdataCompare);
|
||||
@ -2138,6 +2152,10 @@ void LuaScriptInterface::registerFunctions()
|
||||
registerMethod("Player", "hasOutfit", LuaScriptInterface::luaPlayerHasOutfit);
|
||||
registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow);
|
||||
|
||||
registerMethod("Player", "addMount", LuaScriptInterface::luaPlayerAddMount);
|
||||
registerMethod("Player", "removeMount", LuaScriptInterface::luaPlayerRemoveMount);
|
||||
registerMethod("Player", "hasMount", LuaScriptInterface::luaPlayerHasMount);
|
||||
|
||||
registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays);
|
||||
registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays);
|
||||
registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays);
|
||||
@ -8597,6 +8615,79 @@ int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerAddMount(lua_State* L) {
|
||||
// player:addMount(mountId or mountName)
|
||||
Player* player = getUserdata<Player>(L, 1);
|
||||
if (!player) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t mountId;
|
||||
if (isNumber(L, 2)) {
|
||||
mountId = getNumber<uint8_t>(L, 2);
|
||||
}
|
||||
else {
|
||||
Mount* mount = g_game.mounts.getMountByName(getString(L, 2));
|
||||
if (!mount) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
mountId = mount->id;
|
||||
}
|
||||
pushBoolean(L, player->tameMount(mountId));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) {
|
||||
// player:removeMount(mountId or mountName)
|
||||
Player* player = getUserdata<Player>(L, 1);
|
||||
if (!player) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t mountId;
|
||||
if (isNumber(L, 2)) {
|
||||
mountId = getNumber<uint8_t>(L, 2);
|
||||
}
|
||||
else {
|
||||
Mount* mount = g_game.mounts.getMountByName(getString(L, 2));
|
||||
if (!mount) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
mountId = mount->id;
|
||||
}
|
||||
pushBoolean(L, player->untameMount(mountId));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerHasMount(lua_State* L) {
|
||||
// player:hasMount(mountId or mountName)
|
||||
const Player* player = getUserdata<const Player>(L, 1);
|
||||
if (!player) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Mount* mount = nullptr;
|
||||
if (isNumber(L, 2)) {
|
||||
mount = g_game.mounts.getMountByID(getNumber<uint8_t>(L, 2));
|
||||
}
|
||||
else {
|
||||
mount = g_game.mounts.getMountByName(getString(L, 2));
|
||||
}
|
||||
|
||||
if (mount) {
|
||||
pushBoolean(L, player->hasMount(mount));
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L)
|
||||
{
|
||||
// player:getPremiumDays()
|
||||
@ -8823,10 +8914,14 @@ int LuaScriptInterface::luaPlayerGetClient(lua_State* L)
|
||||
// player:getClient()
|
||||
Player* player = getUserdata<Player>(L, 1);
|
||||
if (player) {
|
||||
lua_createtable(L, 0, 2);
|
||||
lua_createtable(L, 0, 5);
|
||||
setField(L, "version", player->getProtocolVersion());
|
||||
setField(L, "os", player->getOperatingSystem());
|
||||
} else {
|
||||
setField(L, "otcv8", player->getOTCv8Version());
|
||||
setField(L, "ping", player->getLocalPing());
|
||||
setField(L, "fps", player->getFPS());
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
@ -11294,11 +11389,15 @@ int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L)
|
||||
int LuaScriptInterface::luaConditionSetOutfit(lua_State* L)
|
||||
{
|
||||
// condition:setOutfit(outfit)
|
||||
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons])
|
||||
// condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons[, lookMount, lookWings, lookAura, lookShader]])
|
||||
Outfit_t outfit;
|
||||
if (isTable(L, 2)) {
|
||||
outfit = getOutfit(L, 2);
|
||||
} else {
|
||||
outfit.lookShader = getNumber<uint16_t>(L, 12, outfit.lookShader);
|
||||
outfit.lookAura = getNumber<uint16_t>(L, 11, outfit.lookAura);
|
||||
outfit.lookWings = getNumber<uint16_t>(L, 10, outfit.lookWings);
|
||||
outfit.lookMount = getNumber<uint16_t>(L, 9, outfit.lookMount);
|
||||
outfit.lookAddons = getNumber<uint8_t>(L, 8, outfit.lookAddons);
|
||||
outfit.lookFeet = getNumber<uint8_t>(L, 7);
|
||||
outfit.lookLegs = getNumber<uint8_t>(L, 6);
|
||||
@ -12277,3 +12376,20 @@ void LuaEnvironment::executeTimerEvent(uint32_t eventIndex)
|
||||
luaL_unref(luaState, LUA_REGISTRYINDEX, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
int LuaScriptInterface::luaCreatureSetProgressbar(lua_State* L)
|
||||
{
|
||||
// creature:sendProgressbar(duration, leftToRight)
|
||||
Creature* creature = getUserdata<Creature>(L, 1);
|
||||
uint32_t duration = getNumber<uint32_t>(L, 2);
|
||||
bool ltr = getBoolean(L, 3);
|
||||
if (creature) {
|
||||
g_game.startProgressbar(creature, duration, ltr);
|
||||
pushBoolean(L, true);
|
||||
}
|
||||
else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
@ -786,6 +786,8 @@ class LuaScriptInterface
|
||||
|
||||
static int luaCreatureGetPathTo(lua_State* L);
|
||||
|
||||
static int luaCreatureSetProgressbar(lua_State* L);
|
||||
|
||||
// Player
|
||||
static int luaPlayerCreate(lua_State* L);
|
||||
|
||||
@ -909,6 +911,10 @@ class LuaScriptInterface
|
||||
static int luaPlayerHasOutfit(lua_State* L);
|
||||
static int luaPlayerSendOutfitWindow(lua_State* L);
|
||||
|
||||
static int luaPlayerAddMount(lua_State* L);
|
||||
static int luaPlayerRemoveMount(lua_State* L);
|
||||
static int luaPlayerHasMount(lua_State* L);
|
||||
|
||||
static int luaPlayerGetPremiumDays(lua_State* L);
|
||||
static int luaPlayerAddPremiumDays(lua_State* L);
|
||||
static int luaPlayerRemovePremiumDays(lua_State* L);
|
||||
|
@ -775,6 +775,23 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa
|
||||
std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl;
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("mount"))) {
|
||||
mType->info.outfit.lookMount = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("aura"))) {
|
||||
mType->info.outfit.lookAura = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("wings"))) {
|
||||
mType->info.outfit.lookWings = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("shader"))) {
|
||||
Shader* shader = g_game.shaders.getShaderByName(attr.as_string());
|
||||
mType->info.outfit.lookShader = shader ? shader->id : 0;
|
||||
}
|
||||
|
||||
if ((attr = node.attribute("corpse"))) {
|
||||
mType->info.lookcorpse = pugi::cast<uint16_t>(attr.value());
|
||||
}
|
||||
|
82
src/mounts.cpp
Normal file
82
src/mounts.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* The Forgotten Server - a free and open-source MMORPG server emulator
|
||||
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "otpch.h"
|
||||
|
||||
#include "mounts.h"
|
||||
|
||||
#include "pugicast.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool Mounts::reload()
|
||||
{
|
||||
mounts.clear();
|
||||
return loadFromXml();
|
||||
}
|
||||
|
||||
bool Mounts::loadFromXml()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file("data/XML/mounts.xml");
|
||||
if (!result) {
|
||||
printXMLError("Error - Mounts::loadFromXml", "data/XML/mounts.xml", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto mountNode : doc.child("mounts").children()) {
|
||||
mounts.emplace_back(
|
||||
static_cast<uint8_t>(pugi::cast<uint16_t>(mountNode.attribute("id").value())),
|
||||
pugi::cast<uint16_t>(mountNode.attribute("clientid").value()),
|
||||
mountNode.attribute("name").as_string(),
|
||||
pugi::cast<int32_t>(mountNode.attribute("speed").value()),
|
||||
mountNode.attribute("premium").as_bool()
|
||||
);
|
||||
}
|
||||
mounts.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
|
||||
Mount* Mounts::getMountByID(uint8_t id)
|
||||
{
|
||||
auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) {
|
||||
return mount.id == id;
|
||||
});
|
||||
|
||||
return it != mounts.end() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
Mount* Mounts::getMountByName(const std::string& name) {
|
||||
auto mountName = name.c_str();
|
||||
for (auto& it : mounts) {
|
||||
if (strcasecmp(mountName, it.name.c_str()) == 0) {
|
||||
return ⁢
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Mount* Mounts::getMountByClientID(uint16_t clientId)
|
||||
{
|
||||
auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const Mount& mount) {
|
||||
return mount.clientId == clientId;
|
||||
});
|
||||
|
||||
return it != mounts.end() ? &*it : nullptr;
|
||||
}
|
52
src/mounts.h
Normal file
52
src/mounts.h
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* The Forgotten Server - a free and open-source MMORPG server emulator
|
||||
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6
|
||||
#define FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6
|
||||
|
||||
struct Mount
|
||||
{
|
||||
Mount(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
|
||||
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
|
||||
|
||||
std::string name;
|
||||
int32_t speed;
|
||||
uint16_t clientId;
|
||||
uint8_t id;
|
||||
bool premium;
|
||||
};
|
||||
|
||||
class Mounts
|
||||
{
|
||||
public:
|
||||
bool reload();
|
||||
bool loadFromXml();
|
||||
Mount* getMountByID(uint8_t id);
|
||||
Mount* getMountByName(const std::string& name);
|
||||
Mount* getMountByClientID(uint16_t clientId);
|
||||
|
||||
const std::vector<Mount>& getMounts() const {
|
||||
return mounts;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Mount> mounts;
|
||||
};
|
||||
|
||||
#endif
|
@ -51,7 +51,7 @@ Position NetworkMessage::getPosition()
|
||||
void NetworkMessage::addString(const std::string& value)
|
||||
{
|
||||
size_t stringLen = value.length();
|
||||
if (!canAdd(stringLen + 2) || stringLen > 8192) {
|
||||
if (!canAdd(stringLen + 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/)
|
||||
|
||||
void NetworkMessage::addBytes(const char* bytes, size_t size)
|
||||
{
|
||||
if (!canAdd(size) || size > 8192) {
|
||||
if (!canAdd(size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,14 @@ public:
|
||||
add_header(info.length);
|
||||
}
|
||||
|
||||
void addCryptoHeader() {
|
||||
void addCryptoHeader(bool addChecksum, bool compression = false) {
|
||||
if (compression) {
|
||||
add_header<uint32_t>(0);
|
||||
}
|
||||
else if (addChecksum) {
|
||||
//add_header(adlerChecksum(buffer + outputBufferStart, info.length));
|
||||
}
|
||||
|
||||
writeMessageLength();
|
||||
}
|
||||
|
||||
|
283
src/player.cpp
283
src/player.cpp
@ -528,6 +528,9 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool
|
||||
);
|
||||
return;
|
||||
}
|
||||
else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE) || IS_IN_KEYRANGE(key, WINGS_RANGE) || IS_IN_KEYRANGE(key, AURAS_RANGE)) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl;
|
||||
return;
|
||||
@ -684,6 +687,31 @@ void Player::sendPing()
|
||||
}
|
||||
}
|
||||
|
||||
void Player::autoOpenContainers()
|
||||
{
|
||||
for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) {
|
||||
Item* item = inventory[i];
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Container* container = item->getContainer()) {
|
||||
if (container->getAutoOpen() >= 0) {
|
||||
addContainer(container->getAutoOpen(), container);
|
||||
onSendContainer(container);
|
||||
}
|
||||
for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) {
|
||||
if (Container* subContainer = (*it)->getContainer()) {
|
||||
if (subContainer->getAutoOpen() >= 0) {
|
||||
addContainer(subContainer->getAutoOpen(), subContainer);
|
||||
onSendContainer(subContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen)
|
||||
{
|
||||
windowTextId = this->windowTextId;
|
||||
@ -3034,6 +3062,11 @@ void Player::updateItemsLight(bool internal /*=false*/)
|
||||
void Player::onAddCondition(ConditionType_t type)
|
||||
{
|
||||
Creature::onAddCondition(type);
|
||||
|
||||
if (type == CONDITION_OUTFIT) {
|
||||
dismount();
|
||||
}
|
||||
|
||||
sendIcons();
|
||||
}
|
||||
|
||||
@ -3825,6 +3858,256 @@ void Player::sendClosePrivate(uint16_t channelId)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Player::getCurrentMount() const
|
||||
{
|
||||
int32_t value;
|
||||
if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) {
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Player::setCurrentMount(uint8_t mountId)
|
||||
{
|
||||
addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId);
|
||||
}
|
||||
|
||||
bool Player::toggleMount(bool mount)
|
||||
{
|
||||
if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) {
|
||||
sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mount) {
|
||||
if (isMounted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) {
|
||||
sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE);
|
||||
return false;
|
||||
}
|
||||
|
||||
const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType);
|
||||
if (!playerOutfit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t currentMountId = getCurrentMount();
|
||||
if (currentMountId == 0) {
|
||||
sendOutfitWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
Mount* currentMount = g_game.mounts.getMountByID(currentMountId);
|
||||
if (!currentMount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasMount(currentMount)) {
|
||||
setCurrentMount(0);
|
||||
sendOutfitWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentMount->premium && !isPremium()) {
|
||||
sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasCondition(CONDITION_OUTFIT)) {
|
||||
sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultOutfit.lookMount = currentMount->clientId;
|
||||
|
||||
if (currentMount->speed != 0) {
|
||||
g_game.changeSpeed(this, currentMount->speed);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!isMounted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dismount();
|
||||
}
|
||||
|
||||
g_game.internalCreatureChangeOutfit(this, defaultOutfit);
|
||||
lastToggleMount = OTSYS_TIME();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::tameMount(uint8_t mountId)
|
||||
{
|
||||
if (!g_game.mounts.getMountByID(mountId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpMountId = mountId - 1;
|
||||
const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
|
||||
|
||||
int32_t value;
|
||||
if (getStorageValue(key, value)) {
|
||||
value |= (1 << (tmpMountId % 31));
|
||||
}
|
||||
else {
|
||||
value = (1 << (tmpMountId % 31));
|
||||
}
|
||||
|
||||
addStorageValue(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::untameMount(uint8_t mountId)
|
||||
{
|
||||
if (!g_game.mounts.getMountByID(mountId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpMountId = mountId - 1;
|
||||
const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31);
|
||||
|
||||
int32_t value;
|
||||
if (!getStorageValue(key, value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
value &= ~(1 << (tmpMountId % 31));
|
||||
addStorageValue(key, value);
|
||||
|
||||
if (getCurrentMount() == mountId) {
|
||||
if (isMounted()) {
|
||||
dismount();
|
||||
g_game.internalCreatureChangeOutfit(this, defaultOutfit);
|
||||
}
|
||||
|
||||
setCurrentMount(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::hasMount(const Mount* mount) const
|
||||
{
|
||||
if (isAccessPlayer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mount->premium && !isPremium()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpMountId = mount->id - 1;
|
||||
|
||||
int32_t value;
|
||||
if (!getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((1 << (tmpMountId % 31)) & value) != 0;
|
||||
}
|
||||
|
||||
void Player::dismount()
|
||||
{
|
||||
Mount* mount = g_game.mounts.getMountByID(getCurrentMount());
|
||||
if (mount && mount->speed > 0) {
|
||||
g_game.changeSpeed(this, -mount->speed);
|
||||
}
|
||||
|
||||
defaultOutfit.lookMount = 0;
|
||||
}
|
||||
|
||||
bool Player::hasWing(const Wing* wing) const
|
||||
{
|
||||
if (isAccessPlayer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wing->premium && !isPremium()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpWingId = wing->id - 1;
|
||||
|
||||
int32_t value;
|
||||
if (!getStorageValue(PSTRG_WINGS_RANGE_START + (tmpWingId / 31), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((1 << (tmpWingId % 31)) & value) != 0;
|
||||
}
|
||||
|
||||
uint8_t Player::getCurrentWing() const
|
||||
{
|
||||
int32_t value;
|
||||
if (getStorageValue(PSTRG_WINGS_CURRENTWINGS, value)) {
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Player::setCurrentWing(uint8_t wingId)
|
||||
{
|
||||
addStorageValue(PSTRG_WINGS_CURRENTWINGS, wingId);
|
||||
}
|
||||
|
||||
bool Player::hasAura(const Aura* aura) const
|
||||
{
|
||||
if (isAccessPlayer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aura->premium && !isPremium()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpAuraId = aura->id - 1;
|
||||
|
||||
int32_t value;
|
||||
if (!getStorageValue(PSTRG_AURAS_RANGE_START + (tmpAuraId / 31), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((1 << (tmpAuraId % 31)) & value) != 0;
|
||||
}
|
||||
|
||||
uint8_t Player::getCurrentAura() const
|
||||
{
|
||||
int32_t value;
|
||||
if (getStorageValue(PSTRG_AURAS_CURRENTAURA, value)) {
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Player::setCurrentAura(uint8_t auraId)
|
||||
{
|
||||
addStorageValue(PSTRG_AURAS_CURRENTAURA, auraId);
|
||||
}
|
||||
|
||||
bool Player::hasShader(const Shader* shader) const
|
||||
{
|
||||
if (isAccessPlayer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shader->premium && !isPremium()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t tmpShaderId = shader->id - 1;
|
||||
|
||||
int32_t value;
|
||||
if (!getStorageValue(PSTRG_SHADERS_RANGE_START + (tmpShaderId / 31), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((1 << (tmpShaderId % 31)) & value) != 0;
|
||||
}
|
||||
|
||||
bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries)
|
||||
{
|
||||
if (tries == 0 || skill == SKILL_LEVEL) {
|
||||
|
78
src/player.h
78
src/player.h
@ -33,6 +33,10 @@
|
||||
#include "guild.h"
|
||||
#include "groups.h"
|
||||
#include "town.h"
|
||||
#include "mounts.h"
|
||||
#include "auras.h"
|
||||
#include "wings.h"
|
||||
#include "shaders.h"
|
||||
|
||||
class BehaviourDatabase;
|
||||
class House;
|
||||
@ -131,6 +135,42 @@ class Player final : public Creature, public Cylinder
|
||||
}
|
||||
std::string getDescription(int32_t lookDistance) const final;
|
||||
|
||||
uint8_t getCurrentMount() const;
|
||||
void setCurrentMount(uint8_t mountId);
|
||||
bool isMounted() const
|
||||
{
|
||||
return defaultOutfit.lookMount != 0;
|
||||
}
|
||||
bool hasMount() const
|
||||
{
|
||||
return defaultOutfit.lookMount != 0;
|
||||
}
|
||||
bool hasAura() const
|
||||
{
|
||||
return defaultOutfit.lookAura != 0;
|
||||
}
|
||||
bool hasWings() const
|
||||
{
|
||||
return defaultOutfit.lookWings != 0;
|
||||
}
|
||||
bool hasShader() const
|
||||
{
|
||||
return defaultOutfit.lookShader != 0;
|
||||
}
|
||||
bool toggleMount(bool mount);
|
||||
bool tameMount(uint8_t mountId);
|
||||
bool untameMount(uint8_t mountId);
|
||||
bool hasMount(const Mount* mount) const;
|
||||
void dismount();
|
||||
|
||||
bool hasWing(const Wing* wing) const;
|
||||
uint8_t getCurrentAura() const;
|
||||
void setCurrentAura(uint8_t auraId);
|
||||
bool hasAura(const Aura* aura) const;
|
||||
uint8_t getCurrentWing() const;
|
||||
void setCurrentWing(uint8_t wingId);
|
||||
bool hasShader(const Shader* shader) const;
|
||||
|
||||
void setGUID(uint32_t guid) {
|
||||
this->guid = guid;
|
||||
}
|
||||
@ -761,6 +801,7 @@ class Player final : public Creature, public Cylinder
|
||||
client->sendInventoryItem(slot, item);
|
||||
}
|
||||
}
|
||||
void autoOpenContainers();
|
||||
|
||||
//event methods
|
||||
void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem,
|
||||
@ -997,10 +1038,39 @@ class Player final : public Creature, public Cylinder
|
||||
}
|
||||
}
|
||||
|
||||
void sendProgressbar(uint32_t id, uint32_t duration, bool ltr = true) {
|
||||
if (client) {
|
||||
client->sendProgressbar(id, duration, ltr);
|
||||
}
|
||||
}
|
||||
|
||||
void receivePing() {
|
||||
lastPong = OTSYS_TIME();
|
||||
}
|
||||
|
||||
void setFPS(uint16_t value)
|
||||
{
|
||||
fps = value;
|
||||
}
|
||||
void setLocalPing(uint16_t value)
|
||||
{
|
||||
localPing = value;
|
||||
}
|
||||
uint16_t getFPS() const
|
||||
{
|
||||
return fps;
|
||||
}
|
||||
uint16_t getLocalPing() const
|
||||
{
|
||||
return localPing;
|
||||
}
|
||||
uint16_t getOTCv8Version() const
|
||||
{
|
||||
if (client)
|
||||
return client->otclientV8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void onThink(uint32_t interval) final;
|
||||
|
||||
void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final;
|
||||
@ -1025,6 +1095,10 @@ class Player final : public Creature, public Cylinder
|
||||
void learnInstantSpell(const std::string& spellName);
|
||||
void forgetInstantSpell(const std::string& spellName);
|
||||
bool hasLearnedInstantSpell(const std::string& spellName) const;
|
||||
const std::map<uint8_t, OpenContainer>& getOpenContainers() const
|
||||
{
|
||||
return openContainers;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::forward_list<Condition*> getMuteConditions() const;
|
||||
@ -1115,6 +1189,7 @@ class Player final : public Creature, public Cylinder
|
||||
int64_t lastPong;
|
||||
int64_t nextAction = 0;
|
||||
int64_t earliestAttackTime = 0;
|
||||
int64_t lastToggleMount = 0;
|
||||
|
||||
BedItem* bedItem = nullptr;
|
||||
Guild* guild = nullptr;
|
||||
@ -1162,6 +1237,8 @@ class Player final : public Creature, public Cylinder
|
||||
|
||||
uint16_t staminaMinutes = 3360;
|
||||
uint16_t maxWriteLen = 0;
|
||||
uint16_t localPing = 0;
|
||||
uint16_t fps = 0;
|
||||
int16_t lastDepotId = -1;
|
||||
|
||||
uint8_t soul = 0;
|
||||
@ -1179,6 +1256,7 @@ class Player final : public Creature, public Cylinder
|
||||
|
||||
bool secureMode = false;
|
||||
bool inMarket = false;
|
||||
bool wasMounted = false;
|
||||
bool ghostMode = false;
|
||||
bool pzLocked = false;
|
||||
bool isConnecting = false;
|
||||
|
@ -25,14 +25,27 @@
|
||||
|
||||
extern RSA g_RSA;
|
||||
|
||||
Protocol::~Protocol()
|
||||
{
|
||||
if (compression) {
|
||||
deflateEnd(&zstream);
|
||||
}
|
||||
}
|
||||
|
||||
void Protocol::onSendMessage(const OutputMessage_ptr& msg) const
|
||||
{
|
||||
if (!rawMessages) {
|
||||
bool compressed = false;
|
||||
if (compression && msg->getLength() > 64) {
|
||||
compress(*msg);
|
||||
compressed = true;
|
||||
}
|
||||
|
||||
msg->writeMessageLength();
|
||||
|
||||
if (encryptionEnabled) {
|
||||
XTEA_encrypt(*msg);
|
||||
msg->addCryptoHeader();
|
||||
msg->addCryptoHeader(checksumEnabled, compressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,3 +166,34 @@ uint32_t Protocol::getIP() const
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Protocol::enableCompression()
|
||||
{
|
||||
if (compression)
|
||||
return;
|
||||
if (deflateInit2(&zstream, 6, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
std::cerr << "ZLIB initialization error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
|
||||
}
|
||||
compression = true;
|
||||
}
|
||||
|
||||
void Protocol::compress(OutputMessage& msg) const
|
||||
{
|
||||
static thread_local std::vector<uint8_t> buffer(NETWORKMESSAGE_MAXSIZE);
|
||||
zstream.next_in = msg.getOutputBuffer();
|
||||
zstream.avail_in = msg.getLength();
|
||||
zstream.next_out = buffer.data();
|
||||
zstream.avail_out = buffer.size();
|
||||
if (deflate(&zstream, Z_SYNC_FLUSH) != Z_OK) {
|
||||
std::cerr << "ZLIB deflate error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
|
||||
return;
|
||||
}
|
||||
int finalSize = buffer.size() - zstream.avail_out - 4;
|
||||
if (finalSize < 0) {
|
||||
std::cerr << "Packet compression error: " << (zstream.msg ? zstream.msg : "unknown") << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
msg.reset();
|
||||
msg.addBytes((const char*)buffer.data(), finalSize);
|
||||
}
|
@ -21,12 +21,13 @@
|
||||
#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1
|
||||
|
||||
#include "connection.h"
|
||||
#include <zlib.h>
|
||||
|
||||
class Protocol : public std::enable_shared_from_this<Protocol>
|
||||
{
|
||||
public:
|
||||
explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {}
|
||||
virtual ~Protocol() = default;
|
||||
virtual ~Protocol();
|
||||
|
||||
// non-copyable
|
||||
Protocol(const Protocol&) = delete;
|
||||
@ -74,6 +75,9 @@ protected:
|
||||
void setXTEAKey(const uint32_t* key) {
|
||||
memcpy(this->key, key, sizeof(*key) * 4);
|
||||
}
|
||||
void disableChecksum() {
|
||||
checksumEnabled = false;
|
||||
}
|
||||
|
||||
void XTEA_encrypt(OutputMessage& msg) const;
|
||||
bool XTEA_decrypt(NetworkMessage& msg) const;
|
||||
@ -82,16 +86,20 @@ protected:
|
||||
void setRawMessages(bool value) {
|
||||
rawMessages = value;
|
||||
}
|
||||
|
||||
void enableCompression();
|
||||
virtual void release() {}
|
||||
friend class Connection;
|
||||
|
||||
OutputMessage_ptr outputBuffer;
|
||||
private:
|
||||
void compress(OutputMessage& msg) const;
|
||||
const ConnectionWeak_ptr connection;
|
||||
uint32_t key[4];
|
||||
bool encryptionEnabled;
|
||||
bool rawMessages;
|
||||
bool encryptionEnabled = false;
|
||||
bool checksumEnabled = true;
|
||||
bool rawMessages = false;
|
||||
bool compression = false;
|
||||
mutable z_stream zstream = { 0 };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -56,6 +56,17 @@ void ProtocolGame::release()
|
||||
|
||||
void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem, bool isFake)
|
||||
{
|
||||
// OTCv8 features and extended opcodes
|
||||
if (otclientV8 || operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
|
||||
if (otclientV8)
|
||||
sendFeatures();
|
||||
NetworkMessage opcodeMessage;
|
||||
opcodeMessage.addByte(0x32);
|
||||
opcodeMessage.addByte(0x00);
|
||||
opcodeMessage.add<uint16_t>(0x00);
|
||||
writeToOutputBuffer(opcodeMessage);
|
||||
}
|
||||
|
||||
//dispatcher thread
|
||||
Player* foundPlayer = g_game.getPlayerByName(name);
|
||||
if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
|
||||
@ -145,6 +156,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS
|
||||
}
|
||||
}
|
||||
|
||||
player->autoOpenContainers();
|
||||
|
||||
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
|
||||
player->registerCreatureEvent("ExtendedOpcode");
|
||||
}
|
||||
@ -197,6 +210,7 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem)
|
||||
|
||||
player->client = getThis();
|
||||
sendAddCreature(player, player->getPosition(), 0, false);
|
||||
player->autoOpenContainers();
|
||||
player->lastIP = player->getIP();
|
||||
player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
|
||||
acceptPackets = true;
|
||||
@ -263,20 +277,18 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
|
||||
enableXTEAEncryption();
|
||||
setXTEAKey(key);
|
||||
|
||||
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
|
||||
NetworkMessage opcodeMessage;
|
||||
opcodeMessage.addByte(0x32);
|
||||
opcodeMessage.addByte(0x00);
|
||||
opcodeMessage.add<uint16_t>(0x00);
|
||||
writeToOutputBuffer(opcodeMessage);
|
||||
}
|
||||
|
||||
msg.skipBytes(1); // gamemaster flag
|
||||
|
||||
uint32_t accountNumber = msg.get<uint32_t>();
|
||||
std::string characterName = msg.getString();
|
||||
std::string password = msg.getString();
|
||||
|
||||
// OTCv8 version detection
|
||||
uint16_t otcV8StringLength = msg.get<uint16_t>();
|
||||
if (otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
|
||||
otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
|
||||
}
|
||||
|
||||
/*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
|
||||
//sendUpdateRequest();
|
||||
disconnectClient("Use Tibia 7.72 to login!");
|
||||
@ -338,8 +350,31 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
|
||||
|
||||
void ProtocolGame::onConnect()
|
||||
{
|
||||
// Checksum missimplementation
|
||||
/* auto output = OutputMessagePool::getOutputMessage();
|
||||
static std::random_device rd;
|
||||
static std::ranlux24 generator(rd());
|
||||
static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
|
||||
|
||||
// Skip checksum
|
||||
output->skipBytes(sizeof(uint32_t));
|
||||
|
||||
// Packet length & type
|
||||
output->add<uint16_t>(0x0006);
|
||||
output->addByte(0x1F);
|
||||
|
||||
// Add timestamp & random number
|
||||
challengeTimestamp = static_cast<uint32_t>(time(nullptr));
|
||||
output->add<uint32_t>(challengeTimestamp);
|
||||
|
||||
challengeRandom = randNumber(generator);
|
||||
output->addByte(challengeRandom);
|
||||
|
||||
// Go back and write checksum
|
||||
output->skipBytes(-12);
|
||||
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
|
||||
|
||||
send(output); */
|
||||
}
|
||||
|
||||
void ProtocolGame::disconnectClient(const std::string& message) const
|
||||
@ -390,6 +425,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
|
||||
case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
|
||||
case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
|
||||
case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
|
||||
case 0x40: parseNewPing(msg); break;
|
||||
case 0x64: parseAutoWalk(msg); break;
|
||||
case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
|
||||
case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
|
||||
@ -445,6 +481,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
|
||||
case 0xCC: parseSeekInContainer(msg); break;
|
||||
case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
|
||||
case 0xD3: parseSetOutfit(msg); break;
|
||||
case 0xD4: parseToggleMount(msg); break;
|
||||
case 0xDC: parseAddVip(msg); break;
|
||||
case 0xDD: parseRemoveVip(msg); break;
|
||||
case 0xE6: parseBugReport(msg); break;
|
||||
@ -735,9 +772,27 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
|
||||
newOutfit.lookLegs = msg.getByte();
|
||||
newOutfit.lookFeet = msg.getByte();
|
||||
newOutfit.lookAddons = msg.getByte();
|
||||
newOutfit.lookMount = msg.get<uint16_t>();
|
||||
newOutfit.lookWings = otclientV8 ? msg.get<uint16_t>() : 0;
|
||||
newOutfit.lookAura = otclientV8 ? msg.get<uint16_t>() : 0;
|
||||
std::string shaderName = otclientV8 ? msg.getString() : "";
|
||||
Shader* shader = g_game.shaders.getShaderByName(shaderName);
|
||||
newOutfit.lookShader = shader ? shader->id : 0;
|
||||
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseToggleMount(NetworkMessage& msg)
|
||||
{
|
||||
int mount = msg.get<int8_t>();
|
||||
int wings = -1, aura = -1, shader = -1;
|
||||
if (otclientV8 >= 254) {
|
||||
wings = msg.get<int8_t>();
|
||||
aura = msg.get<int8_t>();
|
||||
shader = msg.get<int8_t>();
|
||||
}
|
||||
addGameTask(&Game::playerToggleOutfitExtension, player->getID(), mount, wings, aura, shader);
|
||||
}
|
||||
|
||||
void ProtocolGame::parseUseItem(NetworkMessage& msg)
|
||||
{
|
||||
Position pos = msg.getPosition();
|
||||
@ -2127,9 +2182,13 @@ void ProtocolGame::sendOutfitWindow()
|
||||
msg.addByte(0xC8);
|
||||
|
||||
Outfit_t currentOutfit = player->getDefaultOutfit();
|
||||
Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount());
|
||||
if (currentMount) {
|
||||
currentOutfit.lookMount = currentMount->clientId;
|
||||
}
|
||||
|
||||
AddOutfit(msg, currentOutfit);
|
||||
|
||||
const ClientVersion_t clientVersion = g_game.getClientVersion();
|
||||
std::vector<ProtocolOutfit> protocolOutfits;
|
||||
if (player->isAccessPlayer()) {
|
||||
static const std::string gamemasterOutfitName = "Gamemaster";
|
||||
@ -2145,22 +2204,72 @@ void ProtocolGame::sendOutfitWindow()
|
||||
}
|
||||
|
||||
protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
|
||||
if (CLIENT_VERSION_780 <= clientVersion && clientVersion <= CLIENT_VERSION_792) {
|
||||
if (protocolOutfits.size() == 20) { // Game client doesn't allow more than 15 outfits in 780-792
|
||||
if (protocolOutfits.size() == 100) { // Game client doesn't allow more than 100 outfits
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(protocolOutfits.size());
|
||||
for (const ProtocolOutfit& outfit : protocolOutfits) {
|
||||
msg.add<uint16_t>(outfit.lookType);
|
||||
if (clientVersion > CLIENT_VERSION_781) {
|
||||
msg.addString(outfit.name);
|
||||
}
|
||||
msg.addByte(outfit.addons);
|
||||
}
|
||||
|
||||
std::vector<const Mount*> mounts;
|
||||
for (const Mount& mount : g_game.mounts.getMounts()) {
|
||||
if (player->hasMount(&mount)) {
|
||||
mounts.push_back(&mount);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(mounts.size());
|
||||
for (const Mount* mount : mounts) {
|
||||
msg.add<uint16_t>(mount->clientId);
|
||||
msg.addString(mount->name);
|
||||
}
|
||||
|
||||
if (otclientV8) {
|
||||
std::vector<const Wing*> wings;
|
||||
for (const Wing& wing : g_game.wings.getWings()) {
|
||||
if (player->hasWing(&wing)) {
|
||||
wings.push_back(&wing);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(wings.size());
|
||||
for (const Wing* wing : wings) {
|
||||
msg.add<uint16_t>(wing->clientId);
|
||||
msg.addString(wing->name);
|
||||
}
|
||||
|
||||
std::vector<const Aura*> auras;
|
||||
for (const Aura& aura : g_game.auras.getAuras()) {
|
||||
if (player->hasAura(&aura)) {
|
||||
auras.push_back(&aura);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(auras.size());
|
||||
for (const Aura* aura : auras) {
|
||||
msg.add<uint16_t>(aura->clientId);
|
||||
msg.addString(aura->name);
|
||||
}
|
||||
|
||||
std::vector<const Shader*> shaders;
|
||||
for (const Shader& shader : g_game.shaders.getShaders()) {
|
||||
if (player->hasShader(&shader)) {
|
||||
shaders.push_back(&shader);
|
||||
}
|
||||
}
|
||||
|
||||
msg.addByte(shaders.size());
|
||||
for (const Shader* shader : shaders) {
|
||||
msg.add<uint16_t>(shader->id);
|
||||
msg.addString(shader->name);
|
||||
}
|
||||
}
|
||||
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
@ -2284,9 +2393,18 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
|
||||
msg.addByte(outfit.lookLegs);
|
||||
msg.addByte(outfit.lookFeet);
|
||||
msg.addByte(outfit.lookAddons);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
msg.addItemId(outfit.lookTypeEx);
|
||||
}
|
||||
|
||||
msg.add<uint16_t>(outfit.lookMount);
|
||||
if (otclientV8) {
|
||||
msg.add<uint16_t>(outfit.lookWings);
|
||||
msg.add<uint16_t>(outfit.lookAura);
|
||||
Shader* shader = g_game.shaders.getShaderByID(outfit.lookShader);
|
||||
msg.addString(shader ? shader->name : "");
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo)
|
||||
@ -2414,3 +2532,75 @@ void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
|
||||
// process additional opcodes via lua script event
|
||||
addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
|
||||
}
|
||||
|
||||
// OTCv8
|
||||
void ProtocolGame::sendFeatures()
|
||||
{
|
||||
if (!otclientV8)
|
||||
return;
|
||||
|
||||
std::map<GameFeature, bool> features;
|
||||
// place for non-standard OTCv8 features
|
||||
//features[GameExtendedOpcode] = true;
|
||||
//features[GameChangeMapAwareRange] = true;
|
||||
//features[GameNewWalking] = true;
|
||||
//features[GameEnvironmentEffect] = false; // disable it, useless 2 byte with every tile
|
||||
//features[GameExtendedClientPing] = true;
|
||||
//features[GameItemTooltip] = true; // fully available from version 2.6
|
||||
//features[GameWingsAndAura] = true;
|
||||
//features[GameOutfitShaders] = true;
|
||||
|
||||
// packet compression
|
||||
// we don't send feature, because feature assumes all packets are compressed
|
||||
// if adler32 is enabled then compression can be detected automaticly, just adlre32 must be 0
|
||||
if (g_config.getBoolean(ConfigManager::PACKET_COMPRESSION)) {
|
||||
enableCompression();
|
||||
}
|
||||
|
||||
if (features.empty())
|
||||
return;
|
||||
|
||||
auto msg = getOutputBuffer(1024);
|
||||
msg->addByte(0x43);
|
||||
msg->add<uint16_t>(features.size());
|
||||
for (auto& feature : features) {
|
||||
msg->addByte((uint8_t)feature.first);
|
||||
msg->addByte(feature.second ? 1 : 0);
|
||||
}
|
||||
|
||||
send(std::move(getCurrentBuffer())); // send this packet immediately
|
||||
}
|
||||
|
||||
void ProtocolGame::parseNewPing(NetworkMessage& msg)
|
||||
{
|
||||
uint32_t pingId = msg.get<uint32_t>();
|
||||
uint16_t localPing = msg.get<uint16_t>();
|
||||
uint16_t fps = msg.get<uint16_t>();
|
||||
|
||||
addGameTask(&Game::playerReceiveNewPing, player->getID(), localPing, fps);
|
||||
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::sendNewPing, getThis(), pingId)));
|
||||
}
|
||||
|
||||
void ProtocolGame::sendNewPing(uint32_t pingId)
|
||||
{
|
||||
if (!otclientV8)
|
||||
return;
|
||||
|
||||
NetworkMessage msg;
|
||||
msg.addByte(0x40);
|
||||
msg.add<uint32_t>(pingId);
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
||||
|
||||
void ProtocolGame::sendProgressbar(uint32_t id, uint32_t duration, bool ltr)
|
||||
{
|
||||
if (!otclientV8 || otclientV8 < 260)
|
||||
return;
|
||||
|
||||
NetworkMessage msg;
|
||||
msg.addByte(0x3b);
|
||||
msg.add<uint32_t>(id);
|
||||
msg.add<uint32_t>(duration);
|
||||
msg.addByte(ltr);
|
||||
writeToOutputBuffer(msg);
|
||||
}
|
@ -52,6 +52,7 @@ class ProtocolGame final : public Protocol
|
||||
// static protocol information
|
||||
enum { server_sends_first = true };
|
||||
enum { protocol_identifier = 0 }; // Not required as we send first
|
||||
enum { use_checksum = true };
|
||||
|
||||
static const char* protocol_name() {
|
||||
return "gameworld protocol";
|
||||
@ -90,6 +91,7 @@ class ProtocolGame final : public Protocol
|
||||
//Parse methods
|
||||
void parseAutoWalk(NetworkMessage& msg);
|
||||
void parseSetOutfit(NetworkMessage& msg);
|
||||
void parseToggleMount(NetworkMessage& msg);
|
||||
void parseSay(NetworkMessage& msg);
|
||||
void parseLookAt(NetworkMessage& msg);
|
||||
void parseLookInBattleList(NetworkMessage& msg);
|
||||
@ -265,6 +267,12 @@ class ProtocolGame final : public Protocol
|
||||
//otclient
|
||||
void parseExtendedOpcode(NetworkMessage& msg);
|
||||
|
||||
//OTCv8
|
||||
void sendFeatures();
|
||||
void sendProgressbar(uint32_t id, uint32_t duration, bool ltr = true);
|
||||
void parseNewPing(NetworkMessage& msg);
|
||||
void sendNewPing(uint32_t pingId);
|
||||
|
||||
friend class Player;
|
||||
|
||||
// Helpers so we don't need to bind every time
|
||||
@ -289,6 +297,7 @@ class ProtocolGame final : public Protocol
|
||||
|
||||
bool debugAssertSent = false;
|
||||
bool acceptPackets = false;
|
||||
uint16_t otclientV8 = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -193,6 +193,13 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg)
|
||||
return;
|
||||
}
|
||||
|
||||
// OTCv8 version detection
|
||||
uint16_t otclientV8 = 0;
|
||||
uint16_t otcV8StringLength = msg.get<uint16_t>();
|
||||
if (otcV8StringLength == 5 && msg.getString(5) == "OTCv8") {
|
||||
otclientV8 = msg.get<uint16_t>(); // 253, 260, 261, ...
|
||||
}
|
||||
|
||||
auto thisPtr = std::static_pointer_cast<ProtocolLogin>(shared_from_this());
|
||||
g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version)));
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ class ProtocolLogin : public Protocol
|
||||
// static protocol information
|
||||
enum {server_sends_first = false};
|
||||
enum {protocol_identifier = 0x01};
|
||||
enum { use_checksum = true };
|
||||
static const char* protocol_name() {
|
||||
return "login protocol";
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class ProtocolStatus final : public Protocol
|
||||
// static protocol information
|
||||
enum {server_sends_first = false};
|
||||
enum {protocol_identifier = 0xFF};
|
||||
enum { use_checksum = false };
|
||||
static const char* protocol_name() {
|
||||
return "status protocol";
|
||||
}
|
||||
|
52
src/shaders.cpp
Normal file
52
src/shaders.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "otpch.h"
|
||||
|
||||
#include "shaders.h"
|
||||
|
||||
#include "pugicast.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool Shaders::reload()
|
||||
{
|
||||
shaders.clear();
|
||||
return loadFromXml();
|
||||
}
|
||||
|
||||
bool Shaders::loadFromXml()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file("data/XML/shaders.xml");
|
||||
if (!result) {
|
||||
printXMLError("Error - Shaders::loadFromXml", "data/XML/shaders.xml", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto shaderNode : doc.child("shaders").children()) {
|
||||
shaders.emplace_back(
|
||||
static_cast<uint8_t>(pugi::cast<uint16_t>(shaderNode.attribute("id").value())),
|
||||
shaderNode.attribute("name").as_string(),
|
||||
shaderNode.attribute("premium").as_bool()
|
||||
);
|
||||
}
|
||||
shaders.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
|
||||
Shader* Shaders::getShaderByID(uint8_t id)
|
||||
{
|
||||
auto it = std::find_if(shaders.begin(), shaders.end(), [id](const Shader& shader) {
|
||||
return shader.id == id;
|
||||
});
|
||||
|
||||
return it != shaders.end() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
Shader* Shaders::getShaderByName(const std::string& name) {
|
||||
auto shaderName = name.c_str();
|
||||
for (auto& it : shaders) {
|
||||
if (strcasecmp(shaderName, it.name.c_str()) == 0) {
|
||||
return ⁢
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
30
src/shaders.h
Normal file
30
src/shaders.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef FS_SHADERS_H
|
||||
#define FS_SHADERS_H
|
||||
|
||||
struct Shader
|
||||
{
|
||||
Shader(uint8_t id, std::string name, bool premium) :
|
||||
name(std::move(name)), id(id), premium(premium) {}
|
||||
|
||||
uint8_t id;
|
||||
std::string name;
|
||||
bool premium;
|
||||
};
|
||||
|
||||
class Shaders
|
||||
{
|
||||
public:
|
||||
bool reload();
|
||||
bool loadFromXml();
|
||||
Shader* getShaderByID(uint8_t id);
|
||||
Shader* getShaderByName(const std::string& name);
|
||||
|
||||
const std::vector<Shader>& getShaders() const {
|
||||
return shaders;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Shader> shaders;
|
||||
};
|
||||
|
||||
#endif
|
@ -890,6 +890,31 @@ std::string getSkillName(uint8_t skillid)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t adlerChecksum(const uint8_t* data, size_t length)
|
||||
{
|
||||
if (length > NETWORKMESSAGE_MAXSIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint16_t adler = 65521;
|
||||
|
||||
uint32_t a = 1, b = 0;
|
||||
|
||||
while (length > 0) {
|
||||
size_t tmp = length > 5552 ? 5552 : length;
|
||||
length -= tmp;
|
||||
|
||||
do {
|
||||
a += *data++;
|
||||
b += a;
|
||||
} while (--tmp);
|
||||
|
||||
a %= adler;
|
||||
b %= adler;
|
||||
}
|
||||
|
||||
return (b << 16) | a;
|
||||
}
|
||||
|
||||
std::string ucfirst(std::string str)
|
||||
{
|
||||
|
@ -85,6 +85,8 @@ std::string getCombatName(CombatType_t combatType);
|
||||
|
||||
std::string getSkillName(uint8_t skillid);
|
||||
|
||||
uint32_t adlerChecksum(const uint8_t* data, size_t length);
|
||||
|
||||
std::string ucfirst(std::string str);
|
||||
std::string ucwords(std::string str);
|
||||
bool booleanString(const std::string& str);
|
||||
|
63
src/wings.cpp
Normal file
63
src/wings.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "otpch.h"
|
||||
|
||||
#include "wings.h"
|
||||
|
||||
#include "pugicast.h"
|
||||
#include "tools.h"
|
||||
|
||||
bool Wings::reload()
|
||||
{
|
||||
wings.clear();
|
||||
return loadFromXml();
|
||||
}
|
||||
|
||||
bool Wings::loadFromXml()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file("data/XML/wings.xml");
|
||||
if (!result) {
|
||||
printXMLError("Error - Wings::loadFromXml", "data/XML/wings.xml", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto wingNode : doc.child("wings").children()) {
|
||||
wings.emplace_back(
|
||||
static_cast<uint8_t>(pugi::cast<uint16_t>(wingNode.attribute("id").value())),
|
||||
pugi::cast<uint16_t>(wingNode.attribute("clientid").value()),
|
||||
wingNode.attribute("name").as_string(),
|
||||
pugi::cast<int32_t>(wingNode.attribute("speed").value()),
|
||||
wingNode.attribute("premium").as_bool()
|
||||
);
|
||||
}
|
||||
wings.shrink_to_fit();
|
||||
return true;
|
||||
}
|
||||
|
||||
Wing* Wings::getWingByID(uint8_t id)
|
||||
{
|
||||
auto it = std::find_if(wings.begin(), wings.end(), [id](const Wing& wing) {
|
||||
return wing.id == id;
|
||||
});
|
||||
|
||||
return it != wings.end() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
Wing* Wings::getWingByName(const std::string& name) {
|
||||
auto wingName = name.c_str();
|
||||
for (auto& it : wings) {
|
||||
if (strcasecmp(wingName, it.name.c_str()) == 0) {
|
||||
return ⁢
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Wing* Wings::getWingByClientID(uint16_t clientId)
|
||||
{
|
||||
auto it = std::find_if(wings.begin(), wings.end(), [clientId](const Wing& wing) {
|
||||
return wing.clientId == clientId;
|
||||
});
|
||||
|
||||
return it != wings.end() ? &*it : nullptr;
|
||||
}
|
33
src/wings.h
Normal file
33
src/wings.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef FS_WINGS_H
|
||||
#define FS_WINGS_H
|
||||
|
||||
struct Wing
|
||||
{
|
||||
Wing(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) :
|
||||
name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {}
|
||||
|
||||
std::string name;
|
||||
int32_t speed;
|
||||
uint16_t clientId;
|
||||
uint8_t id;
|
||||
bool premium;
|
||||
};
|
||||
|
||||
class Wings
|
||||
{
|
||||
public:
|
||||
bool reload();
|
||||
bool loadFromXml();
|
||||
Wing* getWingByID(uint8_t id);
|
||||
Wing* getWingByName(const std::string& name);
|
||||
Wing* getWingByClientID(uint16_t clientId);
|
||||
|
||||
const std::vector<Wing>& getWings() const {
|
||||
return wings;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Wing> wings;
|
||||
};
|
||||
|
||||
#endif
|
@ -143,6 +143,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\actions.cpp" />
|
||||
<ClCompile Include="..\src\auras.cpp" />
|
||||
<ClCompile Include="..\src\ban.cpp" />
|
||||
<ClCompile Include="..\src\baseevents.cpp" />
|
||||
<ClCompile Include="..\src\bed.cpp" />
|
||||
@ -179,6 +180,7 @@
|
||||
<ClCompile Include="..\src\map.cpp" />
|
||||
<ClCompile Include="..\src\monster.cpp" />
|
||||
<ClCompile Include="..\src\monsters.cpp" />
|
||||
<ClCompile Include="..\src\mounts.cpp" />
|
||||
<ClCompile Include="..\src\movement.cpp" />
|
||||
<ClCompile Include="..\src\networkmessage.cpp" />
|
||||
<ClCompile Include="..\src\npc.cpp" />
|
||||
@ -204,6 +206,7 @@
|
||||
<ClCompile Include="..\src\script.cpp" />
|
||||
<ClCompile Include="..\src\scriptmanager.cpp" />
|
||||
<ClCompile Include="..\src\server.cpp" />
|
||||
<ClCompile Include="..\src\shaders.cpp" />
|
||||
<ClCompile Include="..\src\spawn.cpp" />
|
||||
<ClCompile Include="..\src\spells.cpp" />
|
||||
<ClCompile Include="..\src\protocolstatus.cpp" />
|
||||
@ -216,6 +219,7 @@
|
||||
<ClCompile Include="..\src\vocation.cpp" />
|
||||
<ClCompile Include="..\src\waitlist.cpp" />
|
||||
<ClCompile Include="..\src\wildcardtree.cpp" />
|
||||
<ClCompile Include="..\src\wings.cpp" />
|
||||
<ClCompile Include="..\src\xtea.cpp" />
|
||||
<ClCompile Include="..\src\quests.cpp" />
|
||||
<ClCompile Include="..\src\iomarket.cpp" />
|
||||
@ -223,6 +227,7 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\account.h" />
|
||||
<ClInclude Include="..\src\actions.h" />
|
||||
<ClInclude Include="..\src\auras.h" />
|
||||
<ClInclude Include="..\src\ban.h" />
|
||||
<ClInclude Include="..\src\baseevents.h" />
|
||||
<ClInclude Include="..\src\bed.h" />
|
||||
@ -263,6 +268,7 @@
|
||||
<ClInclude Include="..\src\map.h" />
|
||||
<ClInclude Include="..\src\monster.h" />
|
||||
<ClInclude Include="..\src\monsters.h" />
|
||||
<ClInclude Include="..\src\mounts.h" />
|
||||
<ClInclude Include="..\src\movement.h" />
|
||||
<ClInclude Include="..\src\networkmessage.h" />
|
||||
<ClInclude Include="..\src\npc.h" />
|
||||
@ -282,6 +288,7 @@
|
||||
<ClInclude Include="..\src\script.h" />
|
||||
<ClInclude Include="..\src\scriptmanager.h" />
|
||||
<ClInclude Include="..\src\server.h" />
|
||||
<ClInclude Include="..\src\shaders.h" />
|
||||
<ClInclude Include="..\src\spawn.h" />
|
||||
<ClInclude Include="..\src\spells.h" />
|
||||
<ClInclude Include="..\src\protocolstatus.h" />
|
||||
@ -296,6 +303,7 @@
|
||||
<ClInclude Include="..\src\vocation.h" />
|
||||
<ClInclude Include="..\src\waitlist.h" />
|
||||
<ClInclude Include="..\src\wildcardtree.h" />
|
||||
<ClInclude Include="..\src\wings.h" />
|
||||
<ClInclude Include="..\src\xtea.h" />
|
||||
<ClInclude Include="..\src\quests.h" />
|
||||
<ClInclude Include="..\src\iomarket.h" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user