diff --git a/CMakeLists.txt b/CMakeLists.txt index 268b7f9..0262f47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,44 +5,51 @@ set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) project(tfs) -list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(cotire) -add_compile_options(-Wall -pipe -fvisibility=hidden) -add_definitions(-DBOOST_ALL_NO_LIB) +add_compile_options(-Wall -Werror -pipe -fvisibility=hidden) +set(CMAKE_CXX_FLAGS_PERFORMANCE "${CMAKE_CXX_FLAGS_RELEASE} -march=native") if (CMAKE_COMPILER_IS_GNUCXX) add_compile_options(-fno-strict-aliasing) endif() include(FindCXX11) +include(FindLTO) # Find packages. +find_package(Crypto++ REQUIRED) find_package(GMP REQUIRED) find_package(PugiXML REQUIRED) -find_package(LuaJIT) find_package(MySQL) find_package(Threads) -option(USE_LUAJIT "Use LuaJIT" ${LUAJIT_FOUND}) +# Selects LuaJIT if user defines or auto-detected +if(DEFINED USE_LUAJIT AND NOT USE_LUAJIT) + set(FORCE_LUAJIT ${USE_LUAJIT}) +else() + find_package(LuaJIT) + set(FORCE_LUAJIT ${LuaJIT_FOUND}) +endif() +option(USE_LUAJIT "Use LuaJIT" ${FORCE_LUAJIT}) -if(USE_LUAJIT) - find_package(LuaJIT REQUIRED) +if(FORCE_LUAJIT) if(APPLE) set(CMAKE_EXE_LINKER_FLAGS "-pagezero_size 10000 -image_base 100000000") endif() else() - find_package(Lua) + find_package(Lua REQUIRED) endif() -find_package(Boost 1.53.0 COMPONENTS system filesystem REQUIRED) +find_package(Boost 1.53.0 COMPONENTS system filesystem iostreams REQUIRED) -include(src/CMakeLists.txt) +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}) -target_link_libraries(tfs ${MYSQL_CLIENT_LIBS} ${LUA_LIBRARIES} ${Boost_LIBRARIES} ${Boost_FILESYSTEM_LIBRARIES} ${PUGIXML_LIBRARIES} ${GMP_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +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}) set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h") set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) diff --git a/New Text Document.txt b/New Text Document.txt index 7adc6b7..bf549af 100644 --- a/New Text Document.txt +++ b/New Text Document.txt @@ -1 +1 @@ -next: 17583 \ No newline at end of file +next: 17584 \ No newline at end of file diff --git a/cmake/FindCrypto++.cmake b/cmake/FindCrypto++.cmake new file mode 100644 index 0000000..5f4cb92 --- /dev/null +++ b/cmake/FindCrypto++.cmake @@ -0,0 +1,13 @@ +# Locate Crypto++ library +# This module defines +# Crypto++_FOUND +# Crypto++_INCLUDE_DIR +# Crypto++_LIBRARIES + +find_path(Crypto++_INCLUDE_DIR NAMES cryptopp/cryptlib.h) +find_library(Crypto++_LIBRARIES NAMES cryptopp libcryptopp) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Crypto++ DEFAULT_MSG Crypto++_INCLUDE_DIR Crypto++_LIBRARIES) + +mark_as_advanced(Crypto++_INCLUDE_DIR Crypto++_LIBRARIES) diff --git a/cmake/FindLTO.cmake b/cmake/FindLTO.cmake new file mode 100644 index 0000000..06906ef --- /dev/null +++ b/cmake/FindLTO.cmake @@ -0,0 +1,12 @@ +if(__FIND_LTO_CMAKE__) + return() +endif() +set(__FIND_LTO_CMAKE__ TRUE) + +include(CheckCXXCompilerFlag) +enable_language(CXX) + +check_cxx_compiler_flag("-flto" COMPILER_KNOWS_LTO) +if(COMPILER_KNOWS_LTO) + add_compile_options(-flto) +endif() diff --git a/cmake/FindLuaJIT.cmake b/cmake/FindLuaJIT.cmake index e626a5a..47d986c 100644 --- a/cmake/FindLuaJIT.cmake +++ b/cmake/FindLuaJIT.cmake @@ -10,7 +10,7 @@ find_path(LUA_INCLUDE_DIR luajit.h HINTS ENV LUA_DIR - PATH_SUFFIXES include/luajit-2.0 include + PATH_SUFFIXES include/luajit-2.0 include/luajit-2.1 include PATHS ~/Library/Frameworks /Library/Frameworks diff --git a/cmake/FindMySQL.cmake b/cmake/FindMySQL.cmake index 5e378d5..b3f1a0c 100644 --- a/cmake/FindMySQL.cmake +++ b/cmake/FindMySQL.cmake @@ -67,7 +67,7 @@ IF (WIN32) $ENV{ProgramFiles}/MySQL/*/lib/${libsuffixDist} $ENV{SystemDrive}/MySQL/*/lib/${libsuffixDist}) ELSE (WIN32) - FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient + FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient mariadbclient PATHS $ENV{MYSQL_DIR}/libmysql/.libs $ENV{MYSQL_DIR}/lib @@ -95,7 +95,7 @@ IF (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) FIND_LIBRARY(MYSQL_ZLIB zlib PATHS ${MYSQL_LIB_DIR}) FIND_LIBRARY(MYSQL_YASSL yassl PATHS ${MYSQL_LIB_DIR}) FIND_LIBRARY(MYSQL_TAOCRYPT taocrypt PATHS ${MYSQL_LIB_DIR}) - SET(MYSQL_CLIENT_LIBS mysqlclient) + SET(MYSQL_CLIENT_LIBS ${MYSQL_LIB}) IF (MYSQL_ZLIB) SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} zlib) ENDIF (MYSQL_ZLIB) diff --git a/cmake/FindPugiXML.cmake b/cmake/FindPugiXML.cmake index 4f09e0b..2085b4e 100644 --- a/cmake/FindPugiXML.cmake +++ b/cmake/FindPugiXML.cmake @@ -1,7 +1,15 @@ -find_path(PUGIXML_INCLUDE_DIR NAMES pugixml.hpp) -find_library(PUGIXML_LIBRARIES NAMES pugixml) +if(APPLE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(PC_PUGIXML QUIET pugixml) + set(PUGIXML_DEFINITIONS ${PC_PUGIXML_CFLAGS_OTHER}) + + find_path(PUGIXML_INCLUDE_DIR pugixml.hpp HINTS ${PC_PUGIXML_INCLUDEDIR} ${PC_PUGIXML_INCLUDE_DIRS}) + find_library(PUGIXML_LIBRARIES NAMES pugixml HINTS ${PC_PUGIXML_LIBDIR} ${PC_PUGIXML_LIBRARY_DIRS}) +else() + find_path(PUGIXML_INCLUDE_DIR NAMES pugixml.hpp) + find_library(PUGIXML_LIBRARIES NAMES pugixml) +endif() include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(PugiXML REQUIRED_VARS PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) - +find_package_handle_standard_args(PugiXML REQUIRED_VARS PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) mark_as_advanced(PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) diff --git a/config.lua b/config.lua index 28d6887..5e3fe2e 100644 --- a/config.lua +++ b/config.lua @@ -28,10 +28,10 @@ loginProtocolPort = 7171 gameProtocolPort = 7172 statusProtocolPort = 7171 maxPlayers = 1000 -motd = "Welcome to Nostalrius 4.5!" +motd = "Welcome to Sabrehaven!" onePlayerOnlinePerAccount = true allowClones = false -serverName = "RealOTS" +serverName = "Sabrehaven" statusTimeout = 5000 replaceKickOnLogin = true maxPacketsPerSecond = -1 @@ -42,7 +42,7 @@ moneyRate = 1 -- NOTE: Leave deathLosePercent as -1 if you want to use the default -- death penalty formula. For the old formula, set it to 10. For -- no skill/experience loss, set it to 0. -deathLosePercent = -1 +deathLosePercent = 10 -- Houses houseRentPeriod = "monthly" @@ -83,9 +83,9 @@ newbieLevelThreshold = 5 -- Rates -- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml rateExp = 1 -rateSkill = 1 -rateLoot = 1 -rateMagic = 1 +rateSkill = 5 +rateLoot = 2 +rateMagic = 2 rateSpawn = 0 -- Monsters diff --git a/config.prod.lua b/config.prod.lua new file mode 100644 index 0000000..a53910b --- /dev/null +++ b/config.prod.lua @@ -0,0 +1,109 @@ +-- Combat settings +-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" +worldType = "pvp" +hotkeyAimbotEnabled = true +protectionLevel = 1 +pzLocked = 60000 +removeChargesFromRunes = true +stairJumpExhaustion = 0 +experienceByKillingPlayers = false +expFromPlayersLevelRange = 75 + +-- Skull System +banLength = 30 * 24 * 60 * 60 +whiteSkullTime = 15 * 60 +redSkullTime = 30 * 24 * 60 * 60 +killsDayRedSkull = 3 +killsWeekRedSkull = 5 +killsMonthRedSkull = 10 +killsDayBanishment = 6 +killsWeekBanishment = 10 +killsMonthBanishment = 20 + +-- Connection Config +-- NOTE: maxPlayers set to 0 means no limit +ip = "31.220.54.20" +bindOnlyGlobalAddress = false +loginProtocolPort = 7171 +gameProtocolPort = 7172 +statusProtocolPort = 7171 +maxPlayers = 1000 +motd = "Welcome to Sabrehaven!" +onePlayerOnlinePerAccount = true +allowClones = false +serverName = "Sabrehaven" +statusTimeout = 5000 +replaceKickOnLogin = true +maxPacketsPerSecond = -1 +autoStackCumulatives = false +moneyRate = 1 + +-- Deaths +-- NOTE: Leave deathLosePercent as -1 if you want to use the default +-- death penalty formula. For the old formula, set it to 10. For +-- no skill/experience loss, set it to 0. +deathLosePercent = 10 + +-- Houses +houseRentPeriod = "monthly" + +-- Item Usage +timeBetweenActions = 200 +timeBetweenExActions = 1000 + +-- Map +-- NOTE: set mapName WITHOUT .otbm at the end +mapName = "map" +mapAuthor = "CipSoft" + +-- MySQL +mysqlHost = "127.0.0.1" +mysqlUser = "forgottenserver" +mysqlPass = "DZpJ+UL+t5OgdC0LhxPaz4ae" +mysqlDatabase = "forgottenserver" +mysqlPort = 3306 +mysqlSock = "" + +-- Misc. +allowChangeOutfit = true +freePremium = true +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 4 +showMonsterLoot = false +blockHeight = false +dropItems = false + + +-- Character Rooking +-- Level threshold is the level requirement to teleport players back to newbie town +teleportNewbies = true +newbieTownId = 11 +newbieLevelThreshold = 5 + +-- Rates +-- NOTE: rateExp is not used if you have enabled stages in data/XML/stages.xml +rateExp = 1 +rateSkill = 5 +rateLoot = 2 +rateMagic = 2 +rateSpawn = 0 + +-- Monsters +deSpawnRange = 2 +deSpawnRadius = 50 + +-- Scripts +warnUnsafeScripts = true +convertUnsafeScripts = true + +-- Startup +-- NOTE: defaultPriority only works on Windows and sets process +-- priority, valid values are: "normal", "above-normal", "high" +defaultPriority = "high" +startupDatabaseOptimization = true + +-- Status server information +ownerName = "Erikas" +ownerEmail = "e.kontenis@gmail.com" +url = "https://sabrehaven.com" +location = "France" \ No newline at end of file diff --git a/data/XML/stages.xml b/data/XML/stages.xml index aae0474..0dcfcef 100644 --- a/data/XML/stages.xml +++ b/data/XML/stages.xml @@ -1,9 +1,9 @@ - - - - - + + + + + \ No newline at end of file diff --git a/data/actions/actions.xml b/data/actions/actions.xml index 56ab43c..68b3572 100644 --- a/data/actions/actions.xml +++ b/data/actions/actions.xml @@ -227,6 +227,7 @@ + diff --git a/data/actions/scripts/misc/key.lua b/data/actions/scripts/misc/key.lua index 1e15475..a0937e0 100644 --- a/data/actions/scripts/misc/key.lua +++ b/data/actions/scripts/misc/key.lua @@ -15,6 +15,30 @@ local closedDoors = { [1683] = 1682, [1691] = 1693, [1692] = 1691, + [5097] = 5099, + [5098] = 5097, + [5106] = 5108, + [5107] = 5106, + [5115] = 5117, + [5116] = 5115, + [5124] = 5126, + [5125] = 5124, + [5133] = 5135, + [5134] = 5133, + [5136] = 5138, + [5137] = 5136, + [5139] = 5141, + [5140] = 5139, + [5142] = 5144, + [5143] = 5142, + [5277] = 5279, + [5278] = 5277, + [5280] = 5282, + [5281] = 5280, + [5732] = 5734, + [5733] = 5732, + [5735] = 5737, + [5736] = 5735, } local openDoors = { @@ -26,6 +50,18 @@ local openDoors = { [1673] = 1671, [1684] = 1682, [1693] = 1691, + [5099] = 5097, + [5108] = 5106, + [5117] = 5115, + [5126] = 5124, + [5135] = 5133, + [5138] = 5136, + [5141] = 5139, + [5144] = 5142, + [5279] = 5277, + [5282] = 5280, + [5734] = 5732, + [5737] = 5735, } function onUse(player, item, fromPosition, target, toPosition) diff --git a/data/actions/scripts/misc/rookgard_skip.lua b/data/actions/scripts/misc/rookgard_skip.lua index 366fdb3..89779d8 100644 --- a/data/actions/scripts/misc/rookgard_skip.lua +++ b/data/actions/scripts/misc/rookgard_skip.lua @@ -5,6 +5,16 @@ function onUse(player, item, fromPosition, target, toPosition, isHotkey) item:getPosition():sendMagicEffect(CONST_ME_HITBYFIRE) player:say('OUCH!', TALKTYPE_MONSTER_SAY) player:addExperience(4200 - player:getExperience()) + + player:addItem(3355,1) + player:addItem(3361,1) + player:addItem(3559,1) + player:addItem(3552,1) + player:addItem(3412,1) + player:addItem(3273,1) + player:addItem(3031,25) + player:addItem(3582,3) + player:addItem(3003,1) end return true diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml index acd4814..512315c 100644 --- a/data/creaturescripts/creaturescripts.xml +++ b/data/creaturescripts/creaturescripts.xml @@ -1,6 +1,7 @@ + diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua index bcfb245..480455d 100644 --- a/data/creaturescripts/scripts/login.lua +++ b/data/creaturescripts/scripts/login.lua @@ -55,5 +55,9 @@ function onLogin(player) -- Events player:registerEvent("PlayerDeath") player:registerEvent("kills") + player:registerEvent("PlayerLogout") + player:registerEvent("FirstItems") + player:registerEvent("RegenerateStamina") + return true end diff --git a/data/creaturescripts/scripts/syncoutfit.lua b/data/creaturescripts/scripts/syncoutfit.lua new file mode 100644 index 0000000..ada481d --- /dev/null +++ b/data/creaturescripts/scripts/syncoutfit.lua @@ -0,0 +1,39 @@ +-- Sync outfits that player own with Znote AAC +-- So its possible to see which full sets player +-- has in characterprofile.php + +znote_outfit_list = { + { -- Female (girl) outfits + 136,137,138,139,140,141,142,147,148, + 149,150,155,156,157,158,252,269,270, + 279,288,324,329,336,366,431,433,464, + 466,471,513,514,542,575,578,618,620, + 632,635,636,664,666,683,694,696,698, + 724,732,745,749,759,845,852,874,885, + 900 + }, + { -- Male (boy) outfits + 128,129,130,131,132,133,134,143,144, + 145,146,151,152,153,154,251,268,273, + 278,289,325,328,335,367,430,432,463, + 465,472,512,516,541,574,577,610,619, + 633,634,637,665,667,684,695,697,699, + 725,733,746,750,760,846,853,873,884, + 899 + } +} + +function onLogin(player) + -- storage_value + 1000 storages (highest outfit id) must not be used in other script. + -- Must be identical to Znote AAC config.php: $config['EQ_shower'] -> storage_value + local storage_value = 10000 + -- Loop through outfits + for _, outfit in pairs(znote_outfit_list[player:getSex()+1]) do + if player:hasOutfit(outfit,3) then + if player:getStorageValue(storage_value + outfit) ~= 3 then + player:setStorageValue(storage_value + outfit, 3) + end + end + end + return true +end diff --git a/data/globalevents/globalevents.xml b/data/globalevents/globalevents.xml index 42a865e..bb320ad 100644 --- a/data/globalevents/globalevents.xml +++ b/data/globalevents/globalevents.xml @@ -2,5 +2,6 @@ - + + diff --git a/data/globalevents/scripts/rookgaard_book.lua b/data/globalevents/scripts/rookgaard_book.lua new file mode 100644 index 0000000..97834fb --- /dev/null +++ b/data/globalevents/scripts/rookgaard_book.lua @@ -0,0 +1,16 @@ +local text = { + "Grrr!", + "", + "Shhh!", + "I SMELL FEEEEAAAAAR!", + "CHAMEK ATH UTHUL ARAK!", + "469", + "LET ME OUT!", + "Sacrifice!", + "More! More!" +} + +function onThink(interval, lastExecution) + Position({x=32095, y=32216, z=7}):sendMonsterSay(text[math.random(#text)]) + return true +end \ No newline at end of file diff --git a/data/items/items.srv b/data/items/items.srv index ecdb901..769b318 100644 --- a/data/items/items.srv +++ b/data/items/items.srv @@ -13895,7 +13895,7 @@ Flags = {Take,Expire,ShowDetail} Attributes = {Weight=80,SlotType=RING,HealthTicks=2000,HealthGain=1,ManaTicks=2000,ManaGain=1,ExpireTarget=0,TotalExpireTime=450,DeEquipTarget=3098} TypeID = 3101 -Name = "a spellbook" +Name = "a screaming spellbook" Description = "To humble, or not to humble, that is the question" Flags = {Unmove,Unlay,Unthrow,Unpass,UseEvent} Attributes = {Weight=5800} @@ -14129,282 +14129,282 @@ Attributes = {Weight=120} TypeID = 3148 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3149 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3150 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3151 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3152 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3153 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3154 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3155 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3156 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3157 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3158 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3159 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3160 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3161 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3162 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3163 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3164 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3165 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3166 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3167 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3168 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3169 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3170 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3171 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3172 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3173 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3174 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3175 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3176 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3177 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3178 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3179 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120} TypeID = 3180 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3181 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3182 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3183 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3184 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3185 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3186 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3187 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=2,LightColor=215} TypeID = 3188 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3189 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3190 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3191 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3192 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3193 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3194 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3195 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3196 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3197 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3198 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3199 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3200 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3201 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3202 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3203 Name = "a spell rune" -Flags = {MultiUse,DistUse,Rune,Take,Cumulative} +Flags = {MultiUse,DistUse,Rune,Take} Attributes = {Weight=120,Brightness=1,LightColor=215} TypeID = 3204 diff --git a/data/monster/demon.xml b/data/monster/demon.xml index d746cc6..719792f 100644 --- a/data/monster/demon.xml +++ b/data/monster/demon.xml @@ -79,5 +79,6 @@ + \ No newline at end of file diff --git a/data/movements/movements.xml b/data/movements/movements.xml index 0cc6e30..c1eedd2 100644 --- a/data/movements/movements.xml +++ b/data/movements/movements.xml @@ -446,6 +446,8 @@ + + diff --git a/data/movements/scripts/liberty_bay/citizenship.lua b/data/movements/scripts/liberty_bay/citizenship.lua new file mode 100644 index 0000000..8b3be1d --- /dev/null +++ b/data/movements/scripts/liberty_bay/citizenship.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32317, y = 32826, z = 07}) + creature:getPlayer():setTown(Town("Liberty Bay")) + Game.sendMagicEffect({x = 32317, y = 32826, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32313, y = 32819, z = 07}) + Game.sendMagicEffect({x = 32313, y = 32819, z = 07}, 14) +end diff --git a/data/npc/avar.npc b/data/npc/avar.npc index 5086524..49d0a18 100644 --- a/data/npc/avar.npc +++ b/data/npc/avar.npc @@ -30,4 +30,6 @@ VANISH,! -> "See you later." "carlin" -> "I saved the women there once or twice." "news" -> "There is a great evil lurking beneath this isle ... and beneath the Plains of Havoc and in the ancient necropolis and beneath the Ghostlands ... well everywhere basically." "rumors" -> * +"sabrehaven","talon" -> "I have looted plenty of them. However, it looks like this is not yours level business." +"sabrehaven","talon", level>100 -> "I think you already know about the demons. However I know what you didn't knew about them!" } diff --git a/data/npc/briasol.npc b/data/npc/briasol.npc index 6232969..fcf3365 100644 --- a/data/npc/briasol.npc +++ b/data/npc/briasol.npc @@ -83,6 +83,7 @@ VANISH,! -> "Asha Thrazi." "sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=6 "sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=6 "sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=6 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=8500, "Interesting item. Do you want to sell a Sabrehaven talon for %P gold?", Topic=6 "sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=6 "sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=6 @@ -91,6 +92,7 @@ VANISH,! -> "Asha Thrazi." "sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=6 "sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=6 "sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=6 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=8500*%1, "Interesting items. Do you want to sell %A Sabrehaven talons for %P gold?", Topic=6 Topic=5,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) Topic=5,"yes" -> "Come back, when you have enough money." diff --git a/data/npc/chantalle.npc b/data/npc/chantalle.npc index 59389ff..6c7062b 100644 --- a/data/npc/chantalle.npc +++ b/data/npc/chantalle.npc @@ -24,6 +24,7 @@ VANISH,! -> "Farewell." "do","you","have" -> * "gem" -> "We trade small diamonds, sapphires, rubies, emeralds, and amethysts." "pearl" -> "We trade white and black pearls." +"sabrehaven","talon" -> "This is an extremely rare thing! Sadly, Liberty Bay can't afford any of that." "white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=1 "black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=1 diff --git a/data/npc/elane.npc b/data/npc/elane.npc index 059d7e3..17bff0c 100644 --- a/data/npc/elane.npc +++ b/data/npc/elane.npc @@ -115,7 +115,7 @@ Topic=3 -> "Ok. Then not." "sniper","gloves",Count(5875)<=0 -> "We are always looking for sniper gloves. They are supposed to raise accuracy. If you find a pair, bring them here. Maybe I can offer you a nice trade." "sniper","gloves",QuestValue(17538)=0 -> Type=5875, Amount=1, "You found sniper gloves?! Incredible! Listen, if you give them to me, I will grant you the right to wear the sniper gloves accessory. How about it?", Topic=4 -Topic=4,"yes",Count(Type)>=Amount -> "Great! I hereby grant you the right to wear the sniper gloves as accessory. Congratulations!", Delete(Type), SetQuestValue(17538,1), AddOutfitAddon(137,1), AddOutfitAddon(129,1), EffectOpp(13) +Topic=4,"yes",Count(Type)>=Amount -> "Great! I hereby grant you the right to wear the sniper gloves as accessory. Congratulations!", Delete(Type), SetQuestValue(17538,1), AddOutfitAddon(137,2), AddOutfitAddon(129,2), EffectOpp(13) Topic=4,"yes" -> "You don't have it." Topic=4 -> "Maybe another time." @@ -167,7 +167,7 @@ Topic=10 -> "Maybe another time." "steel",QuestValue(17539)=4 -> "Ah, have you brought one piece of royal steel, draconian steel and hell steel each?", Topic=11 "mission",QuestValue(17539)=4 -> * "task",QuestValue(17539)=4 -> * -Topic=11,"yes",Count(5888)>=1,Count(5889)>=1,Count(5887)>=1 -> "Wow, I'm impressed, %N. Your really are a valuable member of our paladin guild. I shall grant you your reward now. Wear it proudly!", DeleteAmount(5888,1), DeleteAmount(5889,1), DeleteAmount(5887,1), SetQuestValue(17539,5), AddOutfitAddon(137,2), AddOutfitAddon(129,2), EffectOpp(13) +Topic=11,"yes",Count(5888)>=1,Count(5889)>=1,Count(5887)>=1 -> "Wow, I'm impressed, %N. Your really are a valuable member of our paladin guild. I shall grant you your reward now. Wear it proudly!", DeleteAmount(5888,1), DeleteAmount(5889,1), DeleteAmount(5887,1), SetQuestValue(17539,5), AddOutfitAddon(137,1), AddOutfitAddon(129,1), EffectOpp(13) Topic=11,"yes" -> "You don't have that many." Topic=11 -> "Maybe another time." diff --git a/data/npc/elvith.npc b/data/npc/elvith.npc index e673eb5..f487bc1 100644 --- a/data/npc/elvith.npc +++ b/data/npc/elvith.npc @@ -79,7 +79,7 @@ Topic=2 -> "Maybe you will buy it another time." "lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=1 "drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=1 "simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=1 -"love","peom" -> Type=5952, Amount=1, Price=200, "Do you want to buy a love poem for %P gold?", Topic=1 +"love","poem" -> Type=5952, Amount=1, Price=200, "Do you want to buy a love poem for %P gold?", Topic=1 %1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyre for %P gold?", Topic=1 %1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lute for %P gold?", Topic=1 diff --git a/data/npc/gen-bank.ndb b/data/npc/gen-bank.ndb index 2c5ebb6..3514778 100644 --- a/data/npc/gen-bank.ndb +++ b/data/npc/gen-bank.ndb @@ -82,3 +82,57 @@ Topic=99 -> "Well, can I help you with something else #Topic=99,"yes",Count(3035)>=Price -> "Here you are.", Create(3043), Amount=Price, Delete(3035) #Topic=99,"yes" -> "Sorry, you don't have so many platinum coins." #Topic=99 -> "Well, can I help you with something else?" + +# Bank System +"balance" -> Amount=Balance, "Your account balance is %A gold." +"balance",balance>99999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>999999 -> Amount=Balance, "You certainly have made a pretty penny. Your account balance is %A gold." +"balance",balance>9999999 -> Amount=Balance, "You have made ten millions and it still grows! Your account balance is %A gold." +"balance",balance>99999999 -> Amount=Balance, "I think you must be one of the richest inhabitants in the world! Your account balance is %A gold." + +"deposit" -> "You don't have any gold with you." +"deposit",CountMoney>0 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +"deposit","all",CountMoney>0 -> Price=CountMoney, "Would you really like to deposit %P gold?", Topic=82 +"deposit",$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +"deposit","0" -> "You are joking, aren't you??" +"deposit",$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81,$1,0<$1,CountMoney>=$1 -> Price=$1, "Would you really like to deposit %P gold?", Topic=82 +Topic=81,"0" -> "You are joking, aren't you?" +Topic=81,$1,0<$1,CountMoney<$1 -> "You do not have enough gold." +Topic=81 -> "Please tell me how much gold it is you would like to deposit.", Topic=81 +Topic=82,"yes",CountMoney>=Price -> "Alright, we have added the amount of %P gold to your balance. You can withdraw your money anytime you want to.", DeleteMoney, Deposit(Price) +Topic=82,"yes" -> "I am inconsolable, but it seems you have lost your gold. I hope you get it back." +Topic=82 -> "As you wish. Is there something else I can do for you?" + +"withdraw" -> "Please tell me how much gold you would like to withdraw.", Topic=83 +"withdraw",$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +"withdraw","0" -> "Sure, you want nothing you get nothing!" +"withdraw",$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83,$1,0<$1,Balance>=$1 -> Price=$1, "Are you sure you wish to withdraw %P gold from your bank account?", Topic=84 +Topic=83,"0" -> "Sure, you want nothing you get nothing!" +Topic=83,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=83 -> "Please tell me how much gold you would like to withdraw.", Topic=83 +Topic=84,"yes",Balance>=Price -> "Here you are, %P gold. Please let me know if there is something else I can do for you.", CreateMoney, Withdraw(Price) +Topic=84,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=84 -> "The customer is king! Come back anytime you want to if you wish to withdraw your money." + +"transfer" -> "Please tell me the amount of gold you would like to transfer.", Topic=85 +"transfer","0","to" -> "Please think about it. Okay?" +"transfer",$1,0<$1,"to",Balance<$1 -> "There is not enough gold on your account." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=2 -> Price=$1, "Would you really like to transfer %P gold to %S?", Topic=88 +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +"transfer",$1,0<$1,"to",Balance>=$1,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=85,$1,0<$1,Balance>=$1 -> Price=$1, "Who would you like transfer %P gold to?", Topic=86 +Topic=85,"0" -> "Please think about it. Okay?" +Topic=85,$1,0<$1,Balance<$1 -> "There is not enough gold on your account." +Topic=86,Balance>=Price,TransferToPlayerNameState=2 -> "Would you really like to transfer %P gold to %S?", Topic=87 +Topic=86,Balance>=Price,TransferToPlayerNameState=1 -> "I'm afraid this character only holds a junior account at our bank. Do not worry, though. Once he has chosen his vocation or is no longer on Rookgaard, his account will be upgraded." +Topic=86,Balance>=Price,TransferToPlayerNameState=0 -> "This player does not exist." +Topic=87,"yes",Balance>=Price -> "You have transferred %P gold to %S.", Transfer(Price) +Topic=87,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=87 -> "Ok. What is next?" +Topic=88,"yes",Balance>=Price -> "Very well. You have transferred %P gold to %S.", Transfer(Price) +Topic=88,"yes" -> "I am inconsolable, but it seems you don't have that many gold in your bank account." +Topic=88 -> "Alright, is there something else I can do for you?" + + diff --git a/data/npc/hanna.npc b/data/npc/hanna.npc index 6e8b8c8..72cc0af 100644 --- a/data/npc/hanna.npc +++ b/data/npc/hanna.npc @@ -59,6 +59,7 @@ VANISH,! -> "Good bye." "sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 "sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 "sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=9000, "What a rare! Do you want to sell a Sabrehaven talon for %P gold?", Topic=2 "sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 "sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 @@ -67,6 +68,7 @@ VANISH,! -> "Good bye." "sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 "sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 "sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=9000*%1, "What a rare! Do you want to sell %A Sabrehaven talons for %P gold?", Topic=2 Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) Topic=1,"yes" -> "Come back, when you have enough money." diff --git a/data/npc/harsky.npc b/data/npc/harsky.npc index 6cf79c1..3aab3c8 100644 --- a/data/npc/harsky.npc +++ b/data/npc/harsky.npc @@ -9,7 +9,8 @@ Radius = 0 Behaviour = { ADDRESS,"hello$","king",! -> "HAIL TO THE KING!" ADDRESS,"hail$","king",! -> "HAIL TO THE KING!" -ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","sabrehaven",! -> "HAIL TO THE SABREHAVEN!" ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle ADDRESS,"hello$",! -> * ADDRESS,! -> Idle diff --git a/data/npc/iwan.npc b/data/npc/iwan.npc index 944ecbb..2663fec 100644 --- a/data/npc/iwan.npc +++ b/data/npc/iwan.npc @@ -38,6 +38,7 @@ VANISH,! -> "Bye, bye." "gem" -> "You can buy and sell small diamonds, small sapphires, small rubies, small emeralds, and small amethysts." "pearl" -> "There are white and black pearls you can buy or sell." "jewel" -> "Currently you can purchase wedding rings, golden amulets, and ruby necklaces." +"sabrehaven","talon" -> "The Edron Academy is very interested in them, however the King doesn't want to invest that many money!" "wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 "golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 @@ -68,6 +69,7 @@ VANISH,! -> "Bye, bye." "sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 "sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 "sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=9000, "It is your lucky day! Do you want to sell a Sabrehaven talon for %P gold?", Topic=2 "sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 "sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 @@ -76,6 +78,7 @@ VANISH,! -> "Bye, bye." "sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 "sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 "sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=9000*%1, "It is your lucky day! Do you want to sell %A Sabrehaven talons for %P gold?", Topic=2 Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) Topic=1,"yes" -> "Come back, when you have enough money." diff --git a/data/npc/king.npc b/data/npc/king.npc index 6bd6c13..8247ec6 100644 --- a/data/npc/king.npc +++ b/data/npc/king.npc @@ -7,6 +7,7 @@ Home = [32311,32171,6] Radius = 1 Behaviour = { +ADDRESS,"hail","sabrehaven",! -> "Long live the Sabrehaven!" ADDRESS,"hello","king",! -> "I greet thee, my loyal subject." ADDRESS,"hail","king",! -> * ADDRESS,"salutations","king",! -> * diff --git a/data/npc/odemara.npc b/data/npc/odemara.npc index c6c6cea..79cfbec 100644 --- a/data/npc/odemara.npc +++ b/data/npc/odemara.npc @@ -50,6 +50,7 @@ VANISH,! -> "Farewell." "sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 "sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 "sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=10000, "Oh dear! Do you want to sell a Sabrehaven talon for %P gold?", Topic=2 "sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 "sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 @@ -58,6 +59,7 @@ VANISH,! -> "Farewell." "sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 "sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 "sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=10000*%1, "Oh dear! I haven't seen that many for so long! Do you want to sell %A Sabrehaven talons for %P gold?", Topic=2 Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) Topic=1,"yes" -> "I am sorry, but you don't have enough money." diff --git a/data/npc/rachel.npc b/data/npc/rachel.npc index b9ac646..20e168d 100644 --- a/data/npc/rachel.npc +++ b/data/npc/rachel.npc @@ -46,7 +46,9 @@ VANISH,! -> "These impatient young brats!" %1,1<%1,"life","fluid" -> Type=2874, Data=11, Amount=%1, Price=60*%1, "Do you want to buy %A potions of life fluid for %P gold?", Topic=3 "sell","talon" -> Type=3034, Amount=1, Price=320, "Do you want to sell one of the magic gems called talon for %P gold?", Topic=6 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=10500, "Oh my! I could counter offer Edron Academy for this rare! Do you want to sell one of the magic Sabrehaven gems for %P gold?", Topic=6 "sell",%1,1<%1,"talon" -> Type=3034, Amount=%1, Price=320*%1, "Do you want to sell %A magic gems called talon for %P gold?", Topic=6 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=10500*%1, "Oh my! I could counter offer Edron Academy for this rare! Do you want to sell %A magic Sabrehaven gems for %P gold?", Topic=6 Topic=1,"yes" -> "I thought so, what do you want?" Topic=1,"no" -> "First lesson: DON'T LIE TO RACHEL!", Burning(10,4), EffectMe(15), EffectOpp(16) diff --git a/data/npc/sandra.npc b/data/npc/sandra.npc index 5f73958..ec534e0 100644 --- a/data/npc/sandra.npc +++ b/data/npc/sandra.npc @@ -78,7 +78,7 @@ Topic=6,"yes" -> "Great! I've signed you up for our bonus system. From now on, y Topic=6,"no" -> "Alright. I removed your name from our list. If you want to join again and get the chance to win a potion belt addon, just ask me for the 'bonus'.", SetQuestValue(17545,1) Topic=6 -> "Maybe another time." -"deposit",QuestValue(17545)=2 -> "Would you like to get a lottery ticket instead of the deposit for your vials?", Topic=7 +"deposit",QuestValue(17545)=2 -> "Would you like to get a lottery ticket instead of the deposit for your vials?", Data=0, Topic=7 "vial",QuestValue(17545)=2 -> * "flask",QuestValue(17545)=2 -> * Topic=7,"yes",Count(2874)>=100 -> "Ok! Here take this lottery ticket.", DeleteAmount(2874, 100), Type=5957, Amount=1, Create(Type) diff --git a/data/npc/stutch.npc b/data/npc/stutch.npc index 18a7f21..68cf194 100644 --- a/data/npc/stutch.npc +++ b/data/npc/stutch.npc @@ -9,7 +9,8 @@ Radius = 0 Behaviour = { ADDRESS,"hello$","king",! -> "HAIL TO THE KING!" ADDRESS,"hail$","king",! -> "HAIL TO THE KING!" -ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"salutations$","king",! -> "HAIL TO THE KING!" +ADDRESS,"hail$","sabrehaven",! -> "HAIL TO THE SABREHAVEN!" ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle ADDRESS,"hello$",! -> * ADDRESS,! -> Idle diff --git a/data/npc/tezila.npc b/data/npc/tezila.npc index 1a4214f..7588ce1 100644 --- a/data/npc/tezila.npc +++ b/data/npc/tezila.npc @@ -29,6 +29,7 @@ VANISH,! -> "Good bye." "pearl" -> "I have white and black pearls you can buy, but you can also sell me some." "jewel" -> "You can purchase our fine dwarfish wares like wedding rings, golden amulets, and ruby necklaces." "talon" -> "I am suspicious of these magic gems. Better you ask some mages about this." +"sabrehaven","talon" -> "I am more interested in buying them instead of selling." "wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=1 "golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=1 @@ -59,6 +60,7 @@ VANISH,! -> "Good bye." "sell","small","ruby" -> Type=3030, Amount=1, Price=250, "Do you want to sell a small ruby for %P gold?", Topic=2 "sell","small","emerald" -> Type=3032, Amount=1, Price=250, "Do you want to sell a small emerald for %P gold?", Topic=2 "sell","small","amethyst" -> Type=3033, Amount=1, Price=200, "Do you want to sell a small amethyst for %P gold?", Topic=2 +"sell","sabrehaven","talon" -> Type=5776, Amount=1, Price=1000, "Oh, I have plenty of them in my warehouse. However, do you want to sell a Sabrehaven talon for %P gold?", Topic=2 "sell",%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=160*%1, "Do you want to sell %A white pearls for %P gold?", Topic=2 "sell",%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=280*%1, "Do you want to sell %A black pearls for %P gold?", Topic=2 @@ -67,6 +69,7 @@ VANISH,! -> "Good bye." "sell",%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=250*%1, "Do you want to sell %A small rubies for %P gold?", Topic=2 "sell",%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=250*%1, "Do you want to sell %A small emeralds for %P gold?", Topic=2 "sell",%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=200*%1, "Do you want to sell %A small amethysts for %P gold?", Topic=2 +"sell",%1,1<%1,"sabrehaven","talon" -> Type=5776, Amount=%1, Price=1000*%1, "Oh, I have plenty of them in my warehouse. However, do you want to sell %A Sabrehaven talons for %P gold?", Topic=2 Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) Topic=1,"yes" -> "Come back, when you have enough money." diff --git a/data/npc/zoltan.npc b/data/npc/zoltan.npc index a7ee6bf..f34db3f 100644 --- a/data/npc/zoltan.npc +++ b/data/npc/zoltan.npc @@ -31,6 +31,7 @@ VANISH,! -> "Use your knowledge wisely." "rumors" -> * "eremo" -> "He is an old and wise man that has seen a lot of Tibia. He is also one of the best magicians. Visit him on his little island." "visit" -> "You should visit Eremo on his little island. Just ask Pemaret on Cormaya for passage." +"sabrehaven","talon" -> "We know not much yet. However, we are afraid for the variety of horrible ways of how they could be used." "yenny","gentle" -> "Ah, Yenny the Gentle was one of the founders of the druid order called Crunor's Caress, that has been originated in her hometown Carlin." "yenny" -> "Yenny? Which Yenny? That is a common name." diff --git a/data/spells/scripts/runes/fireball.lua b/data/spells/scripts/runes/fireball.lua index a5a32fe..959e796 100644 --- a/data/spells/scripts/runes/fireball.lua +++ b/data/spells/scripts/runes/fireball.lua @@ -5,8 +5,8 @@ combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) combat:setArea(createCombatArea(AREA_CIRCLE2X2)) function onGetFormulaValues(player, level, maglevel) - local base = 20 - local variation = 5 + local base = 30 + local variation = 10 local formula = 3 * maglevel + (2 * level) diff --git a/data/spells/scripts/spells/energy wave.lua b/data/spells/scripts/spells/energy wave.lua index 158850a..9a92f01 100644 --- a/data/spells/scripts/spells/energy wave.lua +++ b/data/spells/scripts/spells/energy wave.lua @@ -4,7 +4,7 @@ combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_TELEPORT) combat:setArea(createCombatArea(AREA_SQUAREWAVE5)) function onGetFormulaValues(player, level, maglevel) - local base = 150 + local base = 250 local variation = 50 local formula = 3 * maglevel + (2 * level) diff --git a/data/talkactions/scripts/buyhouse.lua b/data/talkactions/scripts/buyhouse.lua index 5e3020b..9896bbf 100644 --- a/data/talkactions/scripts/buyhouse.lua +++ b/data/talkactions/scripts/buyhouse.lua @@ -1,36 +1,4 @@ function onSay(player, words, param) - if player:getPremiumDays() <= 0 then - player:sendCancelMessage("You need a premium account.") - return false - end - - local position = player:getPosition() - position:getNextPosition(player:getDirection()) - - local tile = Tile(position) - local house = tile and tile:getHouse() - if house == nil then - player:sendCancelMessage("You have to be looking at the door of the house you would like to buy.") - return false - end - - if house:getOwnerGuid() > 0 then - player:sendCancelMessage("This house already has an owner.") - return false - end - - if player:getHouse() then - player:sendCancelMessage("You are already the owner of a house.") - return false - end - - local price = house:getRent() * 5 - if not player:removeMoney(price) then - player:sendCancelMessage("You do not have enough money.") - return false - end - - house:setOwnerGuid(player:getGuid()) - player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought this house, be sure to have the money for the rent in the bank.") + player:sendTextMessage(MESSAGE_INFO_DESCR, "Buying a house is available only through auction which is available at website: https://sabrehaven.com/house.php") return false end diff --git a/data/talkactions/scripts/minimap_scan.lua b/data/talkactions/scripts/minimap_scan.lua new file mode 100644 index 0000000..519fda4 --- /dev/null +++ b/data/talkactions/scripts/minimap_scan.lua @@ -0,0 +1,114 @@ +local distanceBetweenPositionsX = 8 +local distanceBetweenPositionsY = 8 +local addEventDelay = 500 +local teleportsPerEvent = 1 +local maxEventExecutionTime = 2000 + +local function teleportToClosestPosition(player, x, y, z) + -- direct to position + local tile = Tile(x, y, z) + + if not tile or not tile:getGround() or tile:hasFlag(TILESTATE_TELEPORT) or not player:teleportTo(tile:getPosition()) then + for distance = 1, 3 do + -- try to find some close tile + for changeX = -distance, distance, distance do + for changeY = -distance, distance, distance do + tile = Tile(x + changeX, y + changeY, z) + if tile and tile:getGround() and not tile:hasFlag(TILESTATE_TELEPORT) and player:teleportTo(tile:getPosition()) then + return true + end + end + end + end + + return false + end + + return true +end + +local function sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + local progress = math.floor(((y - minY + (((x - minX) / (maxX - minX)) * distanceBetweenPositionsY)) / (maxY - minY)) * 100) + if progress ~= lastProgress then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan progress: ~' .. progress .. '%') + end + + return progress +end + +local function minimapScan(cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + local player = Player(cid) + + if not player then + --print('Minimap scan stopped - player logged out', cid, minX, maxX, minY, maxY, x, y, z) + return + end + + local scanStartTime = os.mtime() + local teleportsDone = 0 + while true do + if scanStartTime + maxEventExecutionTime < os.mtime() then + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, lastProgress) + break + end + + x = x + distanceBetweenPositionsX + if x > maxX then + x = minX + y = y + distanceBetweenPositionsY + if y > maxY then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan finished: ' .. os.time()) + --print('Minimap scan complete', player:getName(), minX, maxX, minY, maxY, x, y, z) + break + end + end + + if teleportToClosestPosition(player, x, y, z) then + teleportsDone = teleportsDone + 1 + lastProgress = sendScanProgress(player, minX, maxX, minY, maxY, x, y, z, lastProgress) + + --print('Minimap scan teleport', player:getName(), minX, maxX, minY, maxY, x, y, z, progress, teleportsDone) + if teleportsDone == teleportsPerEvent then + addEvent(minimapScan, addEventDelay, cid, minX, maxX, minY, maxY, x, y, z, progress) + break + end + end + end +end + +local function minimapStart(player, minX, maxX, minY, maxY, x, y, z) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Scan started: ' .. os.time()) + --print('Minimap scan start', player:getName(), minX, maxX, minY, maxY, x, y, z) + minimapScan(player:getId(), minX, maxX, minY, maxY, minX - 5, minY, z) +end + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local positions = param:split(',') + if #positions ~= 5 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Command requires 5 parameters: /minimap minX, maxX, minY, maxY, z') + return false + end + + for key, position in pairs(positions) do + local value = tonumber(position) + + if not value then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_ORANGE, 'Invalid parameter ' .. key .. ': ' .. position) + return false + end + + positions[key] = value + end + + minimapStart(player, positions[1], positions[2], positions[3], positions[4], positions[1] - distanceBetweenPositionsX, positions[3], positions[5]) + return false +end \ No newline at end of file diff --git a/data/talkactions/scripts/remove_house.lua b/data/talkactions/scripts/remove_house.lua new file mode 100644 index 0000000..c7f4f86 --- /dev/null +++ b/data/talkactions/scripts/remove_house.lua @@ -0,0 +1,22 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + local position = player:getPosition() + local tile = Tile(position) + local house = tile and tile:getHouse() + if house == nil then + player:sendCancelMessage("You are not inside a house.") + position:sendMagicEffect(CONST_ME_POFF) + return false + end + + house:setOwnerGuid(0) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully removed this house.") + return false +end diff --git a/data/talkactions/scripts/znoteshop.lua b/data/talkactions/scripts/znoteshop.lua new file mode 100644 index 0000000..788ef6a --- /dev/null +++ b/data/talkactions/scripts/znoteshop.lua @@ -0,0 +1,130 @@ +-- Znote Shop v1.0 for Znote AAC on TFS 1.1 +function onSay(player, words, param) + local storage = 54073 -- Make sure to select non-used storage. This is used to prevent SQL load attacks. + local cooldown = 15 -- in seconds. + + if player:getStorageValue(storage) <= os.time() then + player:setStorageValue(storage, os.time() + cooldown) + + local type_desc = { + "itemids", + "pending premium (skip)", + "pending gender change (skip)", + "pending character name change (skip)", + "Outfit and addons", + "Mounts", + "Instant house purchase" + } + print("Player: " .. player:getName() .. " triggered !shop talkaction.") + -- Create the query + local orderQuery = db.storeQuery("SELECT `id`, `type`, `itemid`, `count` FROM `znote_shop_orders` WHERE `account_id` = " .. player:getAccountId() .. ";") + local served = false + + -- Detect if we got any results + if orderQuery ~= false then + repeat + -- Fetch order values + local q_id = result.getNumber(orderQuery, "id") + local q_type = result.getNumber(orderQuery, "type") + local q_itemid = result.getNumber(orderQuery, "itemid") + local q_count = result.getNumber(orderQuery, "count") + + print("Processing type "..q_type..": ".. type_desc[q_type]) + + -- ORDER TYPE 1 (Regular item shop products) + if q_type == 1 then + served = true + -- Get wheight + if player:getFreeCapacity() >= ItemType(q_itemid):getWeight(q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addItem(q_itemid, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received " .. q_count .. " x " .. ItemType(q_itemid):getName() .. "!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Need more CAP!") + end + end + + -- ORDER TYPE 5 (Outfit and addon) + if q_type == 5 then + served = true + + local itemid = q_itemid + local outfits = {} + + if itemid > 1000 then + local first = math.floor(itemid/1000) + table.insert(outfits, first) + itemid = itemid - (first * 1000) + end + table.insert(outfits, itemid) + + for _, outfitId in pairs(outfits) do + -- Make sure player don't already have this outfit and addon + if not player:hasOutfit(outfitId, q_count) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addOutfit(outfitId) + player:addOutfitAddon(outfitId, q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new outfit!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this outfit and addon!") + end + end + end + + -- ORDER TYPE 6 (Mounts) + if q_type == 6 then + served = true + -- Make sure player don't already have this outfit and addon + if not player:hasMount(q_itemid) then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + player:addMount(q_itemid) + player:sendTextMessage(MESSAGE_INFO_DESCR, "Congratulations! You have received a new mount!") + else + player:sendTextMessage(MESSAGE_STATUS_WARNING, "You already have this mount!") + end + end + + -- ORDER TYPE 7 (Direct house purchase) + if q_type == 7 then + served = true + local house = House(q_itemid) + -- Logged in player is not neccesarily the player that bough the house. So we need to load player from db. + print(q_count) + local buyerQuery = db.storeQuery("SELECT `name` FROM `players` WHERE `id` = "..q_count.." LIMIT 1") + + if buyerQuery ~= false then + local buyerName = result.getDataString(buyerQuery, "name") + result.free(buyerQuery) + if house then + db.query("DELETE FROM `znote_shop_orders` WHERE `id` = " .. q_id .. ";") + house:setOwnerGuid(q_count) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully bought the house "..house:getName().." on "..buyerName..", be sure to have the money for the rent in the bank.") + print("Process complete. [".. buyerName .."] has recieved house: ["..house:getName().."]") + end + end + end + + + -- Add custom order types here + -- Type 1 is for itemids (Already coded here) + -- Type 2 is for premium (Coded on web) + -- Type 3 is for gender change (Coded on web) + -- Type 4 is for character name change (Coded on web) + -- Type 5 is for character outfit and addon (Already coded here) + -- Type 6 is for mounts (Already coded here) + -- So use type 7+ for custom stuff, like etc packages. + -- if q_type == 7 then + -- end + until not result.next(orderQuery) + result.free(orderQuery) + if not served then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders to process in-game.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "You have no orders.") + end + else + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Can only be executed once every " .. cooldown .. " seconds. Remaining cooldown: " .. player:getStorageValue(storage) - os.time()) + end + return false +end \ No newline at end of file diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml index de95de2..a0c7fa5 100644 --- a/data/talkactions/talkactions.xml +++ b/data/talkactions/talkactions.xml @@ -36,7 +36,9 @@ - + + + @@ -49,7 +51,8 @@ - + + diff --git a/data/world/map.otbm b/data/world/map.otbm index 3b2de70..f5eff15 100644 Binary files a/data/world/map.otbm and b/data/world/map.otbm differ diff --git a/sabrehaven.sql b/sabrehaven.sql index f995b15..6dd3015 100644 --- a/sabrehaven.sql +++ b/sabrehaven.sql @@ -1498,6 +1498,12 @@ ALTER TABLE `tile_store` -- AUTO_INCREMENT for dumped tables -- +-- +-- AUTO_INCREMENT for table `accounts` +-- +ALTER TABLE `accounts` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; + -- -- AUTO_INCREMENT for table `account_ban_history` -- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5beaa34..c95be84 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,5 +68,5 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp ${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp -) + PARENT_SCOPE) diff --git a/src/actions.cpp b/src/actions.cpp index c23df0d..e1c4113 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -324,7 +324,14 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* player->stopWalk(); if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); } ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); @@ -354,7 +361,15 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& } if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); + uint32_t count = 0; + if (item->isRune()) { + count = player->getRuneCount(item->getID()); + } + else { + count = player->getItemTypeCount(item->getID(), (!item->getFluidType() ? -1 : item->getSubType())); + } + + showUseHotkeyMessage(player, item, count); } if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { diff --git a/src/behaviourdatabase.cpp b/src/behaviourdatabase.cpp index 416559a..49acc94 100644 --- a/src/behaviourdatabase.cpp +++ b/src/behaviourdatabase.cpp @@ -19,6 +19,7 @@ #include "otpch.h" +#include "iologindata.h" #include "behaviourdatabase.h" #include "npc.h" #include "player.h" @@ -174,6 +175,9 @@ bool BehaviourDatabase::loadConditions(ScriptReader& script, NpcBehaviour* behav } else if (script.getSpecial() == '%') { condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT, script.readNumber(), ""); searchTerm = true; + } else if (script.getSpecial() == '$') { + condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, script.readNumber(), ""); + searchTerm = true; } else if (script.getSpecial() == ',') { script.nextToken(); continue; @@ -291,6 +295,9 @@ bool BehaviourDatabase::loadActions(ScriptReader& script, NpcBehaviour* behaviou } else if (identifier == "deposit") { action->type = BEHAVIOUR_TYPE_DEPOSIT; searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "transfer") { + action->type = BEHAVIOUR_TYPE_TRANSFER; + searchType = BEHAVIOUR_PARAMETER_ONE; } else if (identifier == "bless") { action->type = BEHAVIOUR_TYPE_BLESS; searchType = BEHAVIOUR_PARAMETER_ONE; @@ -471,16 +478,24 @@ NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) } if (script.Token == SPECIAL) { - if (script.getSpecial() != '%') { - script.error("illegal character"); - return nullptr; + if (script.getSpecial() == '%') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; + node->number = script.readNumber(); + script.nextToken(); + return node; } - NpcBehaviourNode* node = new NpcBehaviourNode(); - node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; - node->number = script.readNumber(); - script.nextToken(); - return node; + if (script.getSpecial() == '$') { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT; + node->number = script.readNumber(); + script.nextToken(); + return node; + } + + script.error("illegal character"); + return nullptr; } NpcBehaviourNode* node = nullptr; @@ -526,6 +541,9 @@ NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) } else if (identifier == "balance") { node = new NpcBehaviourNode(); node->type = BEHAVIOUR_TYPE_BALANCE; + } else if (identifier == "transfertoplayernamestate") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE; } else if (identifier == "spellknown") { node = new NpcBehaviourNode(); node->type = BEHAVIOUR_TYPE_SPELLKNOWN; @@ -709,6 +727,13 @@ bool BehaviourDatabase::checkCondition(const NpcBehaviourCondition* condition, P } break; } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < condition->number) { + return false; + } + break; + } case BEHAVIOUR_TYPE_STRING: if (!searchWord(condition->string, message)) { return false; @@ -1011,6 +1036,32 @@ void BehaviourDatabase::checkAction(const NpcBehaviourAction* action, Player* pl player->setBankBalance(player->getBankBalance() + money); break; } + case BEHAVIOUR_TYPE_TRANSFER: { + int32_t money = evaluate(action->expression, player, message); + uint16_t state = 0; + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + state = IOLoginData::canTransferMoneyToByName(string); + } + else { + state = transferToPlayer->getVocationId() == 0 ? 1 : 2; + } + + if (state != 2) { + break; + } + + player->setBankBalance(player->getBankBalance() - money); + + if (!transferToPlayer) { + IOLoginData::increaseBankBalance(string, money); + } + else { + transferToPlayer->setBankBalance(transferToPlayer->getBankBalance() + money); + } + + break; + } case BEHAVIOUR_TYPE_BLESS: { uint8_t number = static_cast(evaluate(action->expression, player, message)) - 1; @@ -1159,10 +1210,33 @@ int32_t BehaviourDatabase::evaluate(NpcBehaviourNode* node, Player* player, cons } return value; } + case BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT: { + int32_t value = searchDigitNoLimit(message); + if (value < node->number) { + return false; + } + return value; + } case BEHAVIOUR_TYPE_OPERATION: return checkOperation(player, node, message); case BEHAVIOUR_TYPE_BALANCE: return player->getBankBalance(); + case BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE: { + std::string lowerMessage = asLowerCaseString(message); + if (lowerMessage.find("to ") != std::string::npos) { + string = asCamelCaseString(message.substr(lowerMessage.find("to ") + 3, message.size())); + } + else { + string = asCamelCaseString(message); + } + + Player* transferToPlayer = g_game.getPlayerByName(string); + if (!transferToPlayer) { + return IOLoginData::canTransferMoneyToByName(string); + } + + return transferToPlayer->getVocationId() == 0 ? 1 : 2; + } case BEHAVIOUR_TYPE_SPELLKNOWN: { if (player->hasLearnedInstantSpell(string)) { return true; @@ -1217,6 +1291,17 @@ int32_t BehaviourDatabase::checkOperation(Player* player, NpcBehaviourNode* node } int32_t BehaviourDatabase::searchDigit(const std::string& message) +{ + int32_t value = searchDigitNoLimit(message); + + if (value > 500) { + value = 500; + } + + return value; +} + +int32_t BehaviourDatabase::searchDigitNoLimit(const std::string& message) { int32_t start = -1; int32_t end = -1; @@ -1244,10 +1329,6 @@ int32_t BehaviourDatabase::searchDigit(const std::string& message) return 0; } - if (value > 500) { - value = 500; - } - return value; } @@ -1299,7 +1380,8 @@ std::string BehaviourDatabase::parseResponse(Player* player, const std::string& replaceString(response, "%D", std::to_string(data)); replaceString(response, "%N", player->getName()); replaceString(response, "%P", std::to_string(price)); - + replaceString(response, "%S", string); + int32_t worldTime = g_game.getLightHour(); int32_t hours = std::floor(worldTime / 60); int32_t minutes = worldTime % 60; diff --git a/src/behaviourdatabase.h b/src/behaviourdatabase.h index 72f61d7..ad525bd 100644 --- a/src/behaviourdatabase.h +++ b/src/behaviourdatabase.h @@ -38,6 +38,8 @@ enum NpcBehaviourType_t BEHAVIOUR_TYPE_NUMBER, // return a number BEHAVIOUR_TYPE_OPERATION, // <, =, >, >=, <=, <> BEHAVIOUR_TYPE_MESSAGE_COUNT, // get quantity in player message + BEHAVIOUR_TYPE_MESSAGE_COUNT_NO_LIMIT, // get quantity in player message without any max value restriction + BEHAVIOUR_TYPE_MESSAGE_TRANSFERTOPLAYERNAME_STATE, // set player name parsed fro message to string object and return state if it is possible to transfer BEHAVIOUR_TYPE_IDLE, // idle npc BEHAVIOUR_TYPE_QUEUE, // queue talking creature BEHAVIOUR_TYPE_TOPIC, // get/set topic @@ -269,6 +271,7 @@ class BehaviourDatabase int32_t checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message); int32_t searchDigit(const std::string& message); + int32_t searchDigitNoLimit(const std::string& message); bool searchWord(const std::string& pattern, const std::string& message); std::string parseResponse(Player* player, const std::string& message); diff --git a/src/configmanager.h b/src/configmanager.h index 2d7b0db..c81e761 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -20,7 +20,11 @@ #ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 #define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 +#if __has_include("luajit/lua.hpp") #include +#else +#include +#endif class ConfigManager { diff --git a/src/container.cpp b/src/container.cpp index c694871..f949f81 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -326,7 +326,7 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t } } else { const Item* destItem = getItemByIndex(index); - if (item->equals(destItem) && !destItem->isRune() && destItem->getItemCount() < 100) { + if (item->equals(destItem) && destItem->getItemCount() < 100) { uint32_t remainder = 100 - destItem->getItemCount(); if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) { n = remainder; diff --git a/src/definitions.h b/src/definitions.h index 63c9e08..5aa6634 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -24,7 +24,7 @@ static constexpr auto STATUS_SERVER_NAME = "Sabrehaven"; static constexpr auto STATUS_SERVER_VERSION = "1.0"; static constexpr auto STATUS_SERVER_DEVELOPERS = "OTLand community & Sabrehaven Developers Team"; -static constexpr auto CLIENT_VERSION_MIN = 781; +static constexpr auto CLIENT_VERSION_MIN = 780; static constexpr auto CLIENT_VERSION_MAX = 781; static constexpr auto CLIENT_VERSION_STR = "7.81"; diff --git a/src/game.cpp b/src/game.cpp index 7b55432..c460377 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1019,11 +1019,11 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, item->isRune() ? item->getItemCount() : count, nullptr, 0, player); + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } else { - g_events->eventPlayerOnItemMoved(player, item, item->isRune() ? item->getItemCount() : count, fromPos, toPos, fromCylinder, toCylinder); + g_events->eventPlayerOnItemMoved(player, item, count, fromPos, toPos, fromCylinder, toCylinder); } } @@ -1097,12 +1097,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t m; if (item->isStackable()) { - if (item->isRune()) { - m = std::min(item->getItemCount(), maxQueryCount); - } - else { - m = std::min(count, maxQueryCount); - } + m = std::min(count, maxQueryCount); } else { m = maxQueryCount; @@ -1140,7 +1135,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, if (item->isStackable()) { uint32_t n; - if (!item->isRune() && item->equals(toItem)) { + if (item->equals(toItem)) { n = std::min(100 - toItem->getItemCount(), m); toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); updateItem = toItem; @@ -1240,7 +1235,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde return RETURNVALUE_NOERROR; } - if (item->isStackable() && !item->isRune() && item->equals(toItem)) { + if (item->isStackable() && item->equals(toItem)) { uint32_t m = std::min(item->getItemCount(), maxQueryCount); uint32_t n = std::min(100 - toItem->getItemCount(), m); diff --git a/src/house.cpp b/src/house.cpp index 29f375c..0864b09 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -109,11 +109,6 @@ void House::updateDoorDescription() const ss << "It belongs to house '" << houseName << "'. " << ownerName << " owns this house."; } else { ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; - - const int32_t housePrice = getRent(); - if (housePrice != -1) { - ss << " It costs " << housePrice * 5 << " gold coins."; - } } for (const auto& it : doorSet) { @@ -667,7 +662,9 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const continue; } - if (g_game.removeMoney(player.getDepotLocker(house->getTownId(), true), house->getRent(), FLAG_NOLIMIT)) { + if (player.getBankBalance() >= rent) { + player.setBankBalance(player.getBankBalance() - rent); + time_t paidUntil = currentTime; switch (rentPeriod) { case RENTPERIOD_DAILY: diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 69044ff..61e49ce 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -824,6 +824,20 @@ uint32_t IOLoginData::getGuidByName(const std::string& name) return result->getNumber("id"); } +// Return 0 means player not found, 1 player is without vocation, 2 player with vocation +uint16_t IOLoginData::canTransferMoneyToByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `vocation` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("vocation") == 0 ? 1 : 2; +} + bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) { Database* db = Database::getInstance(); @@ -899,6 +913,15 @@ void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) Database::getInstance()->executeQuery(query.str()); } +void IOLoginData::increaseBankBalance(std::string name, uint64_t bankBalance) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `name` = " << db->escapeString(name); + db->executeQuery(query.str()); +} + bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { Database* db = Database::getInstance(); diff --git a/src/iologindata.h b/src/iologindata.h index 7a1091e..6bfbecd 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -44,11 +44,13 @@ class IOLoginData static bool loadPlayerByName(Player* player, const std::string& name); static bool loadPlayer(Player* player, DBResult_ptr result); static bool savePlayer(Player* player); - static uint32_t getGuidByName(const std::string& name); + static uint32_t getGuidByName(const std::string& name); + static uint16_t canTransferMoneyToByName(const std::string& name); static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name); static std::string getNameByGuid(uint32_t guid); static bool formatPlayerName(std::string& name); static void increaseBankBalance(uint32_t guid, uint64_t bankBalance); + static void increaseBankBalance(const std::string name, uint64_t bankBalance); static bool hasBiddedOnHouse(uint32_t guid); static std::forward_list getVIPEntries(uint32_t accountId); diff --git a/src/item.cpp b/src/item.cpp index acf2f1b..5bb2afe 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -852,17 +852,20 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, s << " for magic level " << it.runeMagLevel; s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; - } else if (it.isDoor() && item) { + } + else if (it.isDoor() && item) { if (item->hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { s << " for level " << item->getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL); } s << "."; - } else if (it.weaponType != WEAPON_NONE) { + } + else if (it.weaponType != WEAPON_NONE) { if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { if (it.attack != 0) { s << ", Atk" << std::showpos << it.attack << std::noshowpos; } - } else if (it.weaponType != WEAPON_AMMO && it.weaponType != WEAPON_WAND && (it.attack != 0 || it.defense != 0)) { + } + else if (it.weaponType != WEAPON_AMMO && it.weaponType != WEAPON_WAND && (it.attack != 0 || it.defense != 0)) { s << " ("; if (it.attack != 0) { s << "Atk:" << static_cast(it.attack); @@ -878,39 +881,49 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, s << ")"; } s << "."; - } else if (it.armor != 0) { + } + else if (it.armor != 0) { if (it.charges > 0) { if (subType > 1) { s << " that has " << static_cast(subType) << " charges left"; - } else { + } + else { s << " that has " << it.charges << " charge left"; } } s << " (Arm:" << it.armor << ")."; - } else if (it.isFluidContainer()) { + } + else if (it.isFluidContainer()) { if (item && item->getFluidType() != 0) { s << " of " << items[item->getFluidType()].name << "."; - } else { + } + else { s << ". It is empty."; } - } else if (it.isSplash()) { + } + else if (it.isSplash()) { s << " of "; if (item && item->getFluidType() != 0) { s << items[item->getFluidType()].name; - } else { + } + else { s << items[1].name; } s << "."; - } else if (it.isContainer() && !it.isChest()) { + } + else if (it.isContainer() && !it.isChest()) { s << " (Vol:" << static_cast(it.maxItems) << ")."; - } else if (it.isKey()) { + } + else if (it.isKey()) { if (item) { s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; - } else { + } + else { s << " (Key:0)."; } - } else if (it.allowDistRead) { + } + else if (it.allowDistRead) { s << "."; s << std::endl; @@ -924,39 +937,49 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, s << " on " << formatDateShort(date); } s << ": "; - } else { + } + else { s << "You read: "; } s << item->getText(); - } else { + } + else { s << "You are too far away to read it."; } - } else { + } + else { s << "Nothing is written on it."; } - } else if (it.charges > 0) { + } + else if (it.charges > 0) { uint32_t charges = (item == nullptr ? it.charges : item->getCharges()); if (charges > 1) { s << " that has " << static_cast(charges) << " charges left."; - } else { + } + else { s << " that has 1 charge left."; } - } else if (it.showDuration) { + } + else if (it.showDuration) { if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { int32_t duration = item->getDuration() / 1000; s << " that has energy for "; if (duration >= 120) { s << duration / 60 << " minutes left."; - } else if (duration > 60) { + } + else if (duration > 60) { s << "1 minute left."; - } else { + } + else { s << "less than a minute left."; } - } else { + } + else { s << " that is brand-new."; } - } else { + } + else { s << "."; } @@ -969,7 +992,8 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, if (it.wieldInfo & WIELDINFO_VOCREQ) { s << it.vocationString; - } else { + } + else { s << "players"; } @@ -980,7 +1004,8 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, if (it.wieldInfo & WIELDINFO_MAGLV) { if (it.wieldInfo & WIELDINFO_LEVEL) { s << " and"; - } else { + } + else { s << " of"; } @@ -999,7 +1024,8 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, if (item && item->getSpecialDescription() != "") { s << std::endl << item->getSpecialDescription().c_str(); - } else if (it.description.length() && lookDistance <= 1) { + } + else if (it.description.length() && lookDistance <= 1) { s << std::endl << it.description << "."; } @@ -1028,7 +1054,8 @@ std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nu } s << (item ? item->getPluralName() : it.getPluralName()); - } else { + } + else { if (addArticle) { const std::string& article = (item ? item->getArticle() : it.article); if (!article.empty()) { @@ -1038,7 +1065,8 @@ std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nu s << name; } - } else { + } + else { s << "an item of type " << it.id; } return s.str(); diff --git a/src/luascript.h b/src/luascript.h index 8fd8a1e..22157a1 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -20,7 +20,11 @@ #ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 #define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 +#if __has_include("luajit/lua.hpp") #include +#else +#include +#endif #if LUA_VERSION_NUM >= 502 #ifndef LUA_COMPAT_ALL diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp index 7f02803..6ad238f 100644 --- a/src/networkmessage.cpp +++ b/src/networkmessage.cpp @@ -105,7 +105,7 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count) add(it.id); } - if (it.stackable) { + if (it.stackable || it.isRune()) { addByte(count); } else if (it.isSplash() || it.isFluidContainer()) { addByte(getLiquidColor(count)); @@ -124,6 +124,8 @@ void NetworkMessage::addItem(const Item* item) if (it.stackable) { addByte(std::min(0xFF, item->getItemCount())); + } else if (it.isRune()) { + addByte(std::min(0xFF, item->getCharges())); } else if (it.isSplash() || it.isFluidContainer()) { addByte(getLiquidColor(item->getFluidType())); } diff --git a/src/party.cpp b/src/party.cpp index e0d80a4..a7a9d8c 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -270,7 +270,7 @@ bool Party::invitePlayer(Player& player) ss << player.getName() << " has been invited."; if (memberList.empty() && inviteList.empty()) { - ss << " Open the party channel to communicate with your members."; + ss << " Open the party channel to communicate with your members. Type !share to enable/disable party experience share."; g_game.updatePlayerShield(leader); leader->sendCreatureSkull(leader); } diff --git a/src/player.cpp b/src/player.cpp index e5d3a6a..7335037 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -2138,7 +2138,7 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { //need an exchange with source? const Item* inventoryItem = getInventoryItem(static_cast(index)); - if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->isRune() || inventoryItem->getID() != item->getID())) { + if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { return RETURNVALUE_NEEDEXCHANGE; } @@ -2207,7 +2207,7 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co } if (destItem) { - if (!destItem->isRune() && destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { + if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { maxQueryCount = 100 - destItem->getItemCount(); } else { @@ -2540,6 +2540,31 @@ uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) con return count; } +// 7.8 mechanics to count runes by charges requires different logic to be implemented +uint32_t Player::getRuneCount(uint16_t itemId) const +{ + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (item->getID() == itemId) { + count += item->getCharges(); + } + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + count += (*it)->getCharges(); + } + } + } + } + return count; +} + bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const { if (amount == 0) { diff --git a/src/player.h b/src/player.h index 0ca4979..7a47cf3 100644 --- a/src/player.h +++ b/src/player.h @@ -972,6 +972,7 @@ class Player final : public Creature, public Cylinder size_t getFirstIndex() const final; size_t getLastIndex() const final; uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + uint32_t getRuneCount(uint16_t itemId) const; std::map& getAllItemTypeCount(std::map& countMap) const final; Thing* getThing(size_t index) const final; diff --git a/src/rsa.cpp b/src/rsa.cpp index ba557ac..b382313 100644 --- a/src/rsa.cpp +++ b/src/rsa.cpp @@ -31,9 +31,13 @@ static CryptoPP::AutoSeededRandomPool prng; void RSA::decrypt(char* msg) const { - CryptoPP::Integer m{ reinterpret_cast(msg), 128 }; - auto c = pk.CalculateInverse(prng, m); - c.Encode(reinterpret_cast(msg), 128); + try { + CryptoPP::Integer m{ reinterpret_cast(msg), 128 }; + auto c = pk.CalculateInverse(prng, m); + c.Encode(reinterpret_cast(msg), 128); + } + catch (const CryptoPP::Exception& e) { + } } static const std::string header = "-----BEGIN RSA PRIVATE KEY-----"; diff --git a/src/spawn.cpp b/src/spawn.cpp index 8b08492..4a39128 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -89,7 +89,7 @@ bool Spawns::loadFromXml(const std::string& filename) spawnList.emplace_front(pos, radius); Spawn& spawn = spawnList.front(); - uint32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; + uint32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 500; if (interval > MINSPAWN_INTERVAL) { uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN); if (exInterval) { diff --git a/src/spells.cpp b/src/spells.cpp index 03500da..7974680 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -1820,7 +1820,7 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t if (target == nullptr) { Tile* toTile = g_game.map.getTile(toPosition); if (toTile) { - const Creature* visibleCreature = toTile->getBottomVisibleCreature(player); + const Creature* visibleCreature = toTile->getTopCreature(); if (visibleCreature) { var.number = visibleCreature->getID(); } @@ -1841,7 +1841,7 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t postCastSpell(player); if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { - int32_t newCount = std::max(0, item->getItemCount() - 1); + int32_t newCount = std::max(0, item->getCharges() - 1); g_game.transformItem(item, item->getID(), newCount); } return true; diff --git a/src/tools.cpp b/src/tools.cpp index 506f512..dc18231 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -360,6 +360,27 @@ std::string asUpperCaseString(std::string source) return source; } +std::string asCamelCaseString(std::string source) { + bool active = true; + + for (int i = 0; source[i] != '\0'; i++) { + if (std::isalpha(source[i])) { + if (active) { + source[i] = std::toupper(source[i]); + active = false; + } + else { + source[i] = std::tolower(source[i]); + } + } + else if (source[i] == ' ') { + active = true; + } + } + + return source; +} + StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) { StringVec returnVector; diff --git a/src/tools.h b/src/tools.h index 8f05a93..e507430 100644 --- a/src/tools.h +++ b/src/tools.h @@ -41,6 +41,7 @@ void trim_left(std::string& source, char t); void toLowerCaseString(std::string& source); std::string asLowerCaseString(std::string source); std::string asUpperCaseString(std::string source); +std::string asCamelCaseString(std::string source); typedef std::vector StringVec; typedef std::vector IntegerVec;