Merge branch '31-prepare-sources-to-build-in-linux-production' into 'master'

Resolve "Prepare sources to build in linux production"

Closes #31

See merge request ErikasKontenis/Sabrehaven!27
This commit is contained in:
Erikas Kontenis 2020-02-07 17:50:46 +00:00
commit 13b743fc52
69 changed files with 1003 additions and 206 deletions

View File

@ -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)

View File

@ -1 +1 @@
next: 17583
next: 17584

13
cmake/FindCrypto++.cmake Normal file
View File

@ -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)

12
cmake/FindLTO.cmake Normal file
View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -1,7 +1,15 @@
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)

View File

@ -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

109
config.prod.lua Normal file
View File

@ -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"

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<stages>
<config enabled="1"/>
<stage minlevel="1" maxlevel="8" multiplier="7"/>
<stage minlevel="9" maxlevel="20" multiplier="6"/>
<stage minlevel="21" maxlevel="50" multiplier="5"/>
<stage minlevel="51" maxlevel="100" multiplier="4"/>
<stage minlevel="101" multiplier="5"/>
<stage minlevel="1" maxlevel="20" multiplier="10" />
<stage minlevel="21" maxlevel="40" multiplier="7" />
<stage minlevel="41" maxlevel="60" multiplier="5" />
<stage minlevel="61" maxlevel="80" multiplier="3" />
<stage minlevel="81" multiplier="2" />
</stages>

View File

@ -227,6 +227,7 @@
<action itemid="4873" script="misc/chests.lua" />
<action itemid="5730" script="misc/chests.lua" />
<action itemid="5742" script="misc/chests.lua" />
<action itemid="5775" script="misc/chests.lua" />
<!-- Fluids -->
<action itemid="2524" script="misc/fluids.lua" />

View File

@ -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)

View File

@ -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

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<creaturescripts>
<event type="login" name="PlayerLogin" script="login.lua"/>
<event type="login" name="SyncOutfit" script="syncoutfit.lua"/>
<event type="logout" name="PlayerLogout" script="logout.lua" />
<event type="login" name="FirstItems" script="firstitems.lua"/>
<event type="login" name="RegenerateStamina" script="regeneratestamina.lua" />

View File

@ -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

View File

@ -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

View File

@ -2,5 +2,6 @@
<globalevents>
<globalevent type="startup" name="ServerStartup" script="startup.lua"/>
<globalevent type="record" name="PlayerRecord" script="record.lua"/>
<globalevent name="Server Save" time="09:55:00" script="serversave.lua" />
<globalevent name="Server Save" time="08:55:00" script="serversave.lua" />
<globalevent name="Rookgaard Book" interval="5000" script="rookgaard_book.lua"/>
</globalevents>

View File

@ -0,0 +1,16 @@
local text = {
"Grrr!",
"<click>",
"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

View File

@ -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

View File

@ -79,5 +79,6 @@
<item id="3049" countmax="1" chance="14" /> <!-- a stealth ring -->
<item id="3034" countmax="1" chance="35" /> <!-- a talon -->
<item id="5954" countmax="1" chance="100" /> <!-- a demon horn -->
<item id="5776" countmax="1" chance="1" /> <!-- a Sabrehaven talon -->
</loot>
</monster>

View File

@ -446,6 +446,8 @@
<!-- Liberty Bay -->
<movevent event="StepIn" movementid="33217" script="liberty_bay/back_cult_piano.lua" />
<movevent event="AddItem" movementid="33217" tileitem="1" script="liberty_bay/back_cult_piano.lua" />
<movevent event="StepIn" movementid="17583" script="liberty_bay/citizenship.lua" />
<movevent event="AddItem" movementid="17583" tileitem="1" script="liberty_bay/citizenship.lua" />
<!-- Floorchange -->
<movevent event="StepIn" itemid="293" script="misc/floorchange.lua" />

View File

@ -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

View File

@ -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!"
}

View File

@ -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."

View File

@ -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

View File

@ -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."

View File

@ -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

View File

@ -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?"

View File

@ -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."

View File

@ -10,6 +10,7 @@ Behaviour = {
ADDRESS,"hello$","king",! -> "HAIL TO THE KING!"
ADDRESS,"hail$","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

View File

@ -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."

View File

@ -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",! -> *

View File

@ -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."

View File

@ -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)

View File

@ -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)

View File

@ -10,6 +10,7 @@ Behaviour = {
ADDRESS,"hello$","king",! -> "HAIL TO THE KING!"
ADDRESS,"hail$","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

View File

@ -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."

View File

@ -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."

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -36,6 +36,8 @@
<talkaction words="/ghost" script="ghost.lua" />
<talkaction words="/clean" script="clean.lua" />
<talkaction words="/storagevalue" separator=" " script="storagevalue.lua" />
<talkaction words="/minimap" separator=" " script="minimap_scan.lua" />
<talkaction words="/removehouse" separator=" " script="remove_house.lua" />
<!-- player talkactions -->
<talkaction words="!buypremium" script="buyprem.lua"/>
@ -49,6 +51,7 @@
<talkaction words="!online" script="online.lua"/>
<talkaction words="!serverinfo" script="serverinfo.lua"/>
<talkaction words="!share" script="experienceshare.lua"/>
<talkaction words="!shop" script="znoteshop.lua"/>
<!-- test talkactions -->
<talkaction words="!z" separator=" " script="magiceffect.lua"/>

Binary file not shown.

View File

@ -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`
--

View File

@ -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)

View File

@ -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)) {

View File

@ -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,11 +478,7 @@ 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();
@ -483,6 +486,18 @@ NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script)
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;
NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE;
@ -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<uint8_t>(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,6 +1380,7 @@ 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<int32_t>(worldTime / 60);

View File

@ -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);

View File

@ -20,7 +20,11 @@
#ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39
#define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39
#if __has_include("luajit/lua.hpp")
#include <luajit/lua.hpp>
#else
#include <lua.hpp>
#endif
class ConfigManager
{

View File

@ -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;

View File

@ -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";

View File

@ -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,13 +1097,8 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder,
uint32_t m;
if (item->isStackable()) {
if (item->isRune()) {
m = std::min<uint32_t>(item->getItemCount(), maxQueryCount);
}
else {
m = std::min<uint32_t>(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<uint32_t>(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<uint32_t>(item->getItemCount(), maxQueryCount);
uint32_t n = std::min<uint32_t>(100 - toItem->getItemCount(), m);

View File

@ -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:

View File

@ -824,6 +824,20 @@ uint32_t IOLoginData::getGuidByName(const std::string& name)
return result->getNumber<uint32_t>("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<uint16_t>("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();

View File

@ -45,10 +45,12 @@ class IOLoginData
static bool loadPlayer(Player* player, DBResult_ptr result);
static bool savePlayer(Player* player);
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<VIPEntry> getVIPEntries(uint32_t accountId);

View File

@ -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<int>(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<int32_t>(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<int>(it.maxItems) << ").";
} else if (it.isKey()) {
}
else if (it.isKey()) {
if (item) {
s << " (Key:" << static_cast<int>(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<int>(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();

View File

@ -20,7 +20,11 @@
#ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8
#define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8
#if __has_include("luajit/lua.hpp")
#include <luajit/lua.hpp>
#else
#include <lua.hpp>
#endif
#if LUA_VERSION_NUM >= 502
#ifndef LUA_COMPAT_ALL

View File

@ -105,7 +105,7 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count)
add<uint16_t>(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<uint16_t>(0xFF, item->getItemCount()));
} else if (it.isRune()) {
addByte(std::min<uint16_t>(0xFF, item->getCharges()));
} else if (it.isSplash() || it.isFluidContainer()) {
addByte(getLiquidColor(item->getFluidType()));
}

View File

@ -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);
}

View File

@ -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<slots_t>(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) {

View File

@ -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<uint32_t, uint32_t>& getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const final;
Thing* getThing(size_t index) const final;

View File

@ -31,10 +31,14 @@ static CryptoPP::AutoSeededRandomPool prng;
void RSA::decrypt(char* msg) const
{
try {
CryptoPP::Integer m{ reinterpret_cast<uint8_t*>(msg), 128 };
auto c = pk.CalculateInverse(prng, m);
c.Encode(reinterpret_cast<uint8_t*>(msg), 128);
}
catch (const CryptoPP::Exception& e) {
}
}
static const std::string header = "-----BEGIN RSA PRIVATE KEY-----";
static const std::string footer = "-----END RSA PRIVATE KEY-----";

View File

@ -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<uint32_t>(childNode.attribute("spawntime").value()) * 1000;
uint32_t interval = pugi::cast<uint32_t>(childNode.attribute("spawntime").value()) * 500;
if (interval > MINSPAWN_INTERVAL) {
uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN);
if (exInterval) {

View File

@ -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<int32_t>(0, item->getItemCount() - 1);
int32_t newCount = std::max<int32_t>(0, item->getCharges() - 1);
g_game.transformItem(item, item->getID(), newCount);
}
return true;

View File

@ -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;

View File

@ -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<std::string> StringVec;
typedef std::vector<int32_t> IntegerVec;