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;