commit 009a571331504af29eb3a0296ef8c6aa336da491 Author: rasanpedromujica Date: Wed Jan 16 17:16:38 2019 -0500 Full Distribution diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6e89e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +vc14/.vs/theforgottenserver/v15/Browse.VC.db +vc14/.vs/theforgottenserver/v15/ipch/ba8106b03bee8153.ipch +<<<<<<< HEAD +*.lastbuildstate +*.tlog +*.ipch +======= +vc14/.vs/theforgottenserver/v15/Browse.VC.opendb +*.ipch +vc14/x64/ +>>>>>>> stored_players +*.exe +vc14/theforgottenserver.vcxproj.user +vc14/.vs/ +*.pdb +*.dll diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..65d1daa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 2.8) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(tfs) + +list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(cotire) + +add_compile_options(-Wall -Werror -pipe -fvisibility=hidden) + +if (CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-fno-strict-aliasing) +endif() + +include(FindCXX11) + +# Find packages. +find_package(GMP REQUIRED) +find_package(PugiXML REQUIRED) +find_package(LuaJIT) +find_package(MySQL) +find_package(Threads) + +option(USE_LUAJIT "Use LuaJIT" ${LUAJIT_FOUND}) + +if(USE_LUAJIT) + find_package(LuaJIT REQUIRED) + if(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "-pagezero_size 10000 -image_base 100000000") + endif() +else() + find_package(Lua) +endif() + +find_package(Boost 1.53.0 COMPONENTS system filesystem REQUIRED) + +include(src/CMakeLists.txt) +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}) + +set_target_properties(tfs PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/otpch.h") +set_target_properties(tfs PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) +cotire(tfs) diff --git a/README.md b/README.md new file mode 100644 index 0000000..16eff96 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +**Edit a file, create a new file, and clone from Bitbucket in under 2 minutes** + +When you're done, you can delete the content in this README and update the file with details for others getting started with your repository. + +*We recommend that you open this README in another tab as you perform the tasks below. You can [watch our video](https://youtu.be/0ocf7u76WSo) for a full demo of all the steps in this tutorial. Open the video in a new tab to avoid leaving Bitbucket.* + +--- + +## Edit a file + +You’ll start by editing this README file to learn how to edit a file in Bitbucket. + +1. Click **Source** on the left side. +2. Click the README.md link from the list of files. +3. Click the **Edit** button. +4. Delete the following text: *Delete this line to make a change to the README from Bitbucket.* +5. After making your change, click **Commit** and then **Commit** again in the dialog. The commit page will open and you’ll see the change you just made. +6. Go back to the **Source** page. + +--- + +## Create a file + +Next, you’ll add a new file to this repository. + +1. Click the **New file** button at the top of the **Source** page. +2. Give the file a filename of **contributors.txt**. +3. Enter your name in the empty file space. +4. Click **Commit** and then **Commit** again in the dialog. +5. Go back to the **Source** page. + +Before you move on, go ahead and explore the repository. You've already seen the **Source** page, but check out the **Commits**, **Branches**, and **Settings** pages. + +--- + +## Clone a repository + +Use these steps to clone from SourceTree, our client for using the repository command-line free. Cloning allows you to work on your files locally. If you don't yet have SourceTree, [download and install first](https://www.sourcetreeapp.com/). If you prefer to clone from the command line, see [Clone a repository](https://confluence.atlassian.com/x/4whODQ). + +1. You’ll see the clone button under the **Source** heading. Click that button. +2. Now click **Check out in SourceTree**. You may need to create a SourceTree account or log in. +3. When you see the **Clone New** dialog in SourceTree, update the destination path and name if you’d like to and then click **Clone**. +4. Open the directory you just created to see your repository’s files. + +Now that you're more familiar with your Bitbucket repository, go ahead and add a new file locally. You can [push your change back to Bitbucket with SourceTree](https://confluence.atlassian.com/x/iqyBMg), or you can [add, commit,](https://confluence.atlassian.com/x/8QhODQ) and [push from the command line](https://confluence.atlassian.com/x/NQ0zDQ). \ No newline at end of file diff --git a/cmake/FindCXX11.cmake b/cmake/FindCXX11.cmake new file mode 100644 index 0000000..2b37e50 --- /dev/null +++ b/cmake/FindCXX11.cmake @@ -0,0 +1,21 @@ +if(__FIND_CXX11_CMAKE__) + return() +endif() +set(__FIND_CXX11_CMAKE__ TRUE) + +include(CheckCXXCompilerFlag) +enable_language(CXX) + +check_cxx_compiler_flag("-std=c++11" COMPILER_KNOWS_CXX11) +if(COMPILER_KNOWS_CXX11) + add_compile_options(-std=c++11) + + # Tested on Mac OS X 10.8.2 with XCode 4.6 Command Line Tools + # Clang requires this to find the correct c++11 headers + check_cxx_compiler_flag("-stdlib=libc++" COMPILER_KNOWS_STDLIB) + if(APPLE AND COMPILER_KNOWS_STDLIB) + add_compile_options(-stdlib=libc++) + endif() +else() + message(FATAL_ERROR "Your C++ compiler does not support C++11.") +endif() diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake new file mode 100644 index 0000000..8080544 --- /dev/null +++ b/cmake/FindGMP.cmake @@ -0,0 +1,14 @@ +# Locate GMP library +# This module defines +# GMP_FOUND +# GMP_INCLUDE_DIR +# GMP_LIBRARIES + +find_path(GMP_INCLUDE_DIR NAMES gmp.h) +find_library(GMP_LIBRARIES NAMES gmp libgmp) +find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES GMPXX_LIBRARIES) + +mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES GMPXX_LIBRARIES) diff --git a/cmake/FindLua.cmake b/cmake/FindLua.cmake new file mode 100644 index 0000000..57a48eb --- /dev/null +++ b/cmake/FindLua.cmake @@ -0,0 +1,118 @@ +# Locate Lua library +# This module defines +# LUA_EXECUTABLE, if found +# LUA_FOUND, if false, do not try to link to Lua +# LUA_LIBRARIES +# LUA_INCLUDE_DIR, where to find lua.h +# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8) +# +# Note that the expected include convention is +# #include "lua.h" +# and not +# #include +# This is because, the lua location is not standardized and may exist +# in locations other than lua/ + +#============================================================================= +# Copyright 2007-2009 Kitware, Inc. +# Modified to support Lua 5.2 by LuaDist 2012 +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) +# +# The required version of Lua can be specified using the +# standard syntax, e.g. FIND_PACKAGE(Lua 5.1) +# Otherwise the module will search for any available Lua implementation + +# Always search for non-versioned lua first (recommended) +SET(_POSSIBLE_LUA_INCLUDE include include/lua) +SET(_POSSIBLE_LUA_EXECUTABLE lua) +SET(_POSSIBLE_LUA_LIBRARY lua) + +# Determine possible naming suffixes (there is no standard for this) +IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}") +ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + SET(_POSSIBLE_SUFFIXES "52" "5.2" "-5.2" "51" "5.1" "-5.1") +ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR) + +# Set up possible search names and locations +FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES}) + LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}") + LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}") + LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}") +ENDFOREACH(_SUFFIX) + +# Find the lua executable +FIND_PROGRAM(LUA_EXECUTABLE + NAMES ${_POSSIBLE_LUA_EXECUTABLE} +) + +# Find the lua header +FIND_PATH(LUA_INCLUDE_DIR lua.h + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE} + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +# Find the lua library +FIND_LIBRARY(LUA_LIBRARY + NAMES ${_POSSIBLE_LUA_LIBRARY} + HINTS + $ENV{LUA_DIR} + PATH_SUFFIXES lib64 lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt +) + +IF(LUA_LIBRARY) + # include the math library for Unix + IF(UNIX AND NOT APPLE) + FIND_LIBRARY(LUA_MATH_LIBRARY m) + SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + ELSE(UNIX AND NOT APPLE) + SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") + ENDIF(UNIX AND NOT APPLE) +ENDIF(LUA_LIBRARY) + +# Determine Lua version +IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h") + FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"") + + STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}") + UNSET(lua_version_str) +ENDIF() + +INCLUDE(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua + REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR + VERSION_VAR LUA_VERSION_STRING) + +MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE) + diff --git a/cmake/FindLuaJIT.cmake b/cmake/FindLuaJIT.cmake new file mode 100644 index 0000000..e626a5a --- /dev/null +++ b/cmake/FindLuaJIT.cmake @@ -0,0 +1,63 @@ +# Locate LuaJIT library +# This module defines +# LUAJIT_FOUND, if false, do not try to link to Lua +# LUA_LIBRARIES +# LUA_INCLUDE_DIR, where to find lua.h +# LUAJIT_VERSION_STRING, the version of Lua found (since CMake 2.8.8) + +## Copied from default CMake FindLua51.cmake + +find_path(LUA_INCLUDE_DIR luajit.h + HINTS + ENV LUA_DIR + PATH_SUFFIXES include/luajit-2.0 include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +find_library(LUA_LIBRARY + NAMES luajit-5.1 + HINTS + ENV LUA_DIR + PATH_SUFFIXES lib + PATHS + ~/Library/Frameworks + /Library/Frameworks + /sw + /opt/local + /opt/csw + /opt +) + +if(LUA_LIBRARY) + # include the math library for Unix + if(UNIX AND NOT APPLE) + find_library(LUA_MATH_LIBRARY m) + set( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries") + # For Windows and Mac, don't need to explicitly include the math library + else() + set( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries") + endif() +endif() + +if(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h") + file(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" luajit_version_str REGEX "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT .+\"") + + string(REGEX REPLACE "^#define[ \t]+LUAJIT_VERSION[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUAJIT_VERSION_STRING "${luajit_version_str}") + unset(luajit_version_str) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if +# all listed variables are TRUE +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJIT + REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR + VERSION_VAR LUAJIT_VERSION_STRING) + +mark_as_advanced(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY) + diff --git a/cmake/FindMySQL.cmake b/cmake/FindMySQL.cmake new file mode 100644 index 0000000..5e378d5 --- /dev/null +++ b/cmake/FindMySQL.cmake @@ -0,0 +1,118 @@ +#-------------------------------------------------------- +# Copyright (C) 1995-2007 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# There are special exceptions to the terms and conditions of the GPL +# as it is applied to this software. View the full text of the exception +# in file LICENSE.exceptions in the top-level directory of this software +# distribution. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# The MySQL Connector/ODBC is licensed under the terms of the +# GPL, like most MySQL Connectors. There are special exceptions +# to the terms and conditions of the GPL as it is applied to +# this software, see the FLOSS License Exception available on +# mysql.com. + +########################################################################## + + +#-------------- FIND MYSQL_INCLUDE_DIR ------------------ +FIND_PATH(MYSQL_INCLUDE_DIR mysql.h + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + /usr/include/mysql + /usr/local/include/mysql + /opt/mysql/mysql/include + /opt/mysql/mysql/include/mysql + /opt/mysql/include + /opt/local/include/mysql5 + /usr/local/mysql/include + /usr/local/mysql/include/mysql + $ENV{ProgramFiles}/MySQL/*/include + $ENV{SystemDrive}/MySQL/*/include) + +#----------------- FIND MYSQL_LIB_DIR ------------------- +IF (WIN32) + # Set lib path suffixes + # dist = for mysql binary distributions + # build = for custom built tree + IF (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(libsuffixDist debug) + SET(libsuffixBuild Debug) + ELSE (CMAKE_BUILD_TYPE STREQUAL Debug) + SET(libsuffixDist opt) + SET(libsuffixBuild Release) + ADD_DEFINITIONS(-DDBUG_OFF) + ENDIF (CMAKE_BUILD_TYPE STREQUAL Debug) + + FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient + PATHS + $ENV{MYSQL_DIR}/lib/${libsuffixDist} + $ENV{MYSQL_DIR}/libmysql + $ENV{MYSQL_DIR}/libmysql/${libsuffixBuild} + $ENV{MYSQL_DIR}/client/${libsuffixBuild} + $ENV{MYSQL_DIR}/libmysql/${libsuffixBuild} + $ENV{ProgramFiles}/MySQL/*/lib/${libsuffixDist} + $ENV{SystemDrive}/MySQL/*/lib/${libsuffixDist}) +ELSE (WIN32) + FIND_LIBRARY(MYSQL_LIB NAMES mysqlclient + PATHS + $ENV{MYSQL_DIR}/libmysql/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/lib/mysql + /usr/local/lib/mysql + /usr/local/mysql/lib + /usr/local/mysql/lib/mysql + /opt/local/mysql5/lib + /opt/local/lib/mysql5/mysql + /opt/mysql/mysql/lib/mysql + /opt/mysql/lib/mysql) +ENDIF (WIN32) + +IF(MYSQL_LIB) + GET_FILENAME_COMPONENT(MYSQL_LIB_DIR ${MYSQL_LIB} PATH) +ENDIF(MYSQL_LIB) + +IF (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + SET(MYSQL_FOUND TRUE) + + INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIR}) + LINK_DIRECTORIES(${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) + IF (MYSQL_ZLIB) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} zlib) + ENDIF (MYSQL_ZLIB) + IF (MYSQL_YASSL) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} yassl) + ENDIF (MYSQL_YASSL) + IF (MYSQL_TAOCRYPT) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} taocrypt) + ENDIF (MYSQL_TAOCRYPT) + # Added needed mysqlclient dependencies on Windows + IF (WIN32) + SET(MYSQL_CLIENT_LIBS ${MYSQL_CLIENT_LIBS} ws2_32) + ENDIF (WIN32) + + MESSAGE(STATUS "MySQL Include dir: ${MYSQL_INCLUDE_DIR} library dir: ${MYSQL_LIB_DIR}") + MESSAGE(STATUS "MySQL client libraries: ${MYSQL_CLIENT_LIBS}") +ELSE (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + MESSAGE(FATAL_ERROR "Cannot find MySQL. Include dir: ${MYSQL_INCLUDE_DIR} library dir: ${MYSQL_LIB_DIR}") +ENDIF (MYSQL_INCLUDE_DIR AND MYSQL_LIB_DIR) + diff --git a/cmake/FindPugiXML.cmake b/cmake/FindPugiXML.cmake new file mode 100644 index 0000000..4f09e0b --- /dev/null +++ b/cmake/FindPugiXML.cmake @@ -0,0 +1,7 @@ +find_path(PUGIXML_INCLUDE_DIR NAMES pugixml.hpp) +find_library(PUGIXML_LIBRARIES NAMES pugixml) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(PugiXML REQUIRED_VARS PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) + +mark_as_advanced(PUGIXML_INCLUDE_DIR PUGIXML_LIBRARIES) diff --git a/cmake/cotire.cmake b/cmake/cotire.cmake new file mode 100644 index 0000000..a4fb533 --- /dev/null +++ b/cmake/cotire.cmake @@ -0,0 +1,3827 @@ +# - cotire (compile time reducer) +# +# See the cotire manual for usage hints. +# +#============================================================================= +# Copyright 2012-2015 Sascha Kratky +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +#============================================================================= + +if(__COTIRE_INCLUDED) + return() +endif() +set(__COTIRE_INCLUDED TRUE) + +# call cmake_minimum_required, but prevent modification of the CMake policy stack in include mode +# cmake_minimum_required also sets the policy version as a side effect, which we have to avoid +if (NOT CMAKE_SCRIPT_MODE_FILE) + cmake_policy(PUSH) +endif() +cmake_minimum_required(VERSION 2.8.12) +if (NOT CMAKE_SCRIPT_MODE_FILE) + cmake_policy(POP) +endif() + +set (COTIRE_CMAKE_MODULE_FILE "${CMAKE_CURRENT_LIST_FILE}") +set (COTIRE_CMAKE_MODULE_VERSION "1.7.6") + +# activate select policies +if (POLICY CMP0025) + # Compiler id for Apple Clang is now AppleClang + cmake_policy(SET CMP0025 NEW) +endif() + +if (POLICY CMP0026) + # disallow use of the LOCATION target property + cmake_policy(SET CMP0026 NEW) +endif() + +if (POLICY CMP0038) + # targets may not link directly to themselves + cmake_policy(SET CMP0038 NEW) +endif() + +if (POLICY CMP0039) + # utility targets may not have link dependencies + cmake_policy(SET CMP0039 NEW) +endif() + +if (POLICY CMP0040) + # target in the TARGET signature of add_custom_command() must exist + cmake_policy(SET CMP0040 NEW) +endif() + +if (POLICY CMP0045) + # error on non-existent target in get_target_property + cmake_policy(SET CMP0045 NEW) +endif() + +if (POLICY CMP0046) + # error on non-existent dependency in add_dependencies + cmake_policy(SET CMP0046 NEW) +endif() + +if (POLICY CMP0049) + # do not expand variables in target source entries + cmake_policy(SET CMP0049 NEW) +endif() + +if (POLICY CMP0050) + # disallow add_custom_command SOURCE signatures + cmake_policy(SET CMP0050 NEW) +endif() + +if (POLICY CMP0051) + # include TARGET_OBJECTS expressions in a target's SOURCES property + cmake_policy(SET CMP0051 NEW) +endif() + +if (POLICY CMP0053) + # simplify variable reference and escape sequence evaluation + cmake_policy(SET CMP0053 NEW) +endif() + +if (POLICY CMP0054) + # only interpret if() arguments as variables or keywords when unquoted + cmake_policy(SET CMP0054 NEW) +endif() + +include(CMakeParseArguments) +include(ProcessorCount) + +function (cotire_get_configuration_types _configsVar) + set (_configs "") + if (CMAKE_CONFIGURATION_TYPES) + list (APPEND _configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + if (CMAKE_BUILD_TYPE) + list (APPEND _configs "${CMAKE_BUILD_TYPE}") + endif() + if (_configs) + list (REMOVE_DUPLICATES _configs) + set (${_configsVar} ${_configs} PARENT_SCOPE) + else() + set (${_configsVar} "None" PARENT_SCOPE) + endif() +endfunction() + +function (cotire_get_source_file_extension _sourceFile _extVar) + # get_filename_component returns extension from first occurrence of . in file name + # this function computes the extension from last occurrence of . in file name + string (FIND "${_sourceFile}" "." _index REVERSE) + if (_index GREATER -1) + math (EXPR _index "${_index} + 1") + string (SUBSTRING "${_sourceFile}" ${_index} -1 _sourceExt) + else() + set (_sourceExt "") + endif() + set (${_extVar} "${_sourceExt}" PARENT_SCOPE) +endfunction() + +macro (cotire_check_is_path_relative_to _path _isRelativeVar) + set (${_isRelativeVar} FALSE) + if (IS_ABSOLUTE "${_path}") + foreach (_dir ${ARGN}) + file (RELATIVE_PATH _relPath "${_dir}" "${_path}") + if (NOT _relPath OR (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.")) + set (${_isRelativeVar} TRUE) + break() + endif() + endforeach() + endif() +endmacro() + +function (cotire_filter_language_source_files _language _target _sourceFilesVar _excludedSourceFilesVar _cotiredSourceFilesVar) + if (CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) + set (_languageExtensions "${CMAKE_${_language}_SOURCE_FILE_EXTENSIONS}") + else() + set (_languageExtensions "") + endif() + if (CMAKE_${_language}_IGNORE_EXTENSIONS) + set (_ignoreExtensions "${CMAKE_${_language}_IGNORE_EXTENSIONS}") + else() + set (_ignoreExtensions "") + endif() + if (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS) + set (_excludeExtensions "${COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS}") + else() + set (_excludeExtensions "") + endif() + if (COTIRE_DEBUG AND _languageExtensions) + message (STATUS "${_language} source file extensions: ${_languageExtensions}") + endif() + if (COTIRE_DEBUG AND _ignoreExtensions) + message (STATUS "${_language} ignore extensions: ${_ignoreExtensions}") + endif() + if (COTIRE_DEBUG AND _excludeExtensions) + message (STATUS "${_language} exclude extensions: ${_excludeExtensions}") + endif() + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_allSourceFiles ${ARGN}) + else() + # as of CMake 3.1 target sources may contain generator expressions + # since we cannot obtain required property information about source files added + # through generator expressions at configure time, we filter them out + string (GENEX_STRIP "${ARGN}" _allSourceFiles) + endif() + set (_filteredSourceFiles "") + set (_excludedSourceFiles "") + foreach (_sourceFile ${_allSourceFiles}) + get_source_file_property(_sourceIsHeaderOnly "${_sourceFile}" HEADER_FILE_ONLY) + get_source_file_property(_sourceIsExternal "${_sourceFile}" EXTERNAL_OBJECT) + get_source_file_property(_sourceIsSymbolic "${_sourceFile}" SYMBOLIC) + if (NOT _sourceIsHeaderOnly AND NOT _sourceIsExternal AND NOT _sourceIsSymbolic) + cotire_get_source_file_extension("${_sourceFile}" _sourceExt) + if (_sourceExt) + list (FIND _ignoreExtensions "${_sourceExt}" _ignoreIndex) + if (_ignoreIndex LESS 0) + list (FIND _excludeExtensions "${_sourceExt}" _excludeIndex) + if (_excludeIndex GREATER -1) + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (FIND _languageExtensions "${_sourceExt}" _sourceIndex) + if (_sourceIndex GREATER -1) + # consider source file unless it is excluded explicitly + get_source_file_property(_sourceIsExcluded "${_sourceFile}" COTIRE_EXCLUDED) + if (_sourceIsExcluded) + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (APPEND _filteredSourceFiles "${_sourceFile}") + endif() + else() + get_source_file_property(_sourceLanguage "${_sourceFile}" LANGUAGE) + if ("${_sourceLanguage}" STREQUAL "${_language}") + # add to excluded sources, if file is not ignored and has correct language without having the correct extension + list (APPEND _excludedSourceFiles "${_sourceFile}") + endif() + endif() + endif() + endif() + endif() + endif() + endforeach() + # separate filtered source files from already cotired ones + # the COTIRE_TARGET property of a source file may be set while a target is being processed by cotire + set (_sourceFiles "") + set (_cotiredSourceFiles "") + foreach (_sourceFile ${_filteredSourceFiles}) + get_source_file_property(_sourceIsCotired "${_sourceFile}" COTIRE_TARGET) + if (_sourceIsCotired) + list (APPEND _cotiredSourceFiles "${_sourceFile}") + else() + get_source_file_property(_sourceCompileFlags "${_sourceFile}" COMPILE_FLAGS) + if (_sourceCompileFlags) + # add to excluded sources, if file has custom compile flags + list (APPEND _excludedSourceFiles "${_sourceFile}") + else() + list (APPEND _sourceFiles "${_sourceFile}") + endif() + endif() + endforeach() + if (COTIRE_DEBUG) + if (_sourceFiles) + message (STATUS "Filtered ${_target} ${_language} sources: ${_sourceFiles}") + endif() + if (_excludedSourceFiles) + message (STATUS "Excluded ${_target} ${_language} sources: ${_excludedSourceFiles}") + endif() + if (_cotiredSourceFiles) + message (STATUS "Cotired ${_target} ${_language} sources: ${_cotiredSourceFiles}") + endif() + endif() + set (${_sourceFilesVar} ${_sourceFiles} PARENT_SCOPE) + set (${_excludedSourceFilesVar} ${_excludedSourceFiles} PARENT_SCOPE) + set (${_cotiredSourceFilesVar} ${_cotiredSourceFiles} PARENT_SCOPE) +endfunction() + +function (cotire_get_objects_with_property_on _filteredObjectsVar _property _type) + set (_filteredObjects "") + foreach (_object ${ARGN}) + get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (_propertyValue) + list (APPEND _filteredObjects "${_object}") + endif() + endif() + endforeach() + set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) +endfunction() + +function (cotire_get_objects_with_property_off _filteredObjectsVar _property _type) + set (_filteredObjects "") + foreach (_object ${ARGN}) + get_property(_isSet ${_type} "${_object}" PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (NOT _propertyValue) + list (APPEND _filteredObjects "${_object}") + endif() + endif() + endforeach() + set (${_filteredObjectsVar} ${_filteredObjects} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_file_property_values _valuesVar _property) + set (_values "") + foreach (_sourceFile ${ARGN}) + get_source_file_property(_propertyValue "${_sourceFile}" ${_property}) + if (_propertyValue) + list (APPEND _values "${_propertyValue}") + endif() + endforeach() + set (${_valuesVar} ${_values} PARENT_SCOPE) +endfunction() + +function (cotire_resolve_config_properites _configurations _propertiesVar) + set (_properties "") + foreach (_property ${ARGN}) + if ("${_property}" MATCHES "") + foreach (_config ${_configurations}) + string (TOUPPER "${_config}" _upperConfig) + string (REPLACE "" "${_upperConfig}" _configProperty "${_property}") + list (APPEND _properties ${_configProperty}) + endforeach() + else() + list (APPEND _properties ${_property}) + endif() + endforeach() + set (${_propertiesVar} ${_properties} PARENT_SCOPE) +endfunction() + +function (cotire_copy_set_properites _configurations _type _source _target) + cotire_resolve_config_properites("${_configurations}" _properties ${ARGN}) + foreach (_property ${_properties}) + get_property(_isSet ${_type} ${_source} PROPERTY ${_property} SET) + if (_isSet) + get_property(_propertyValue ${_type} ${_source} PROPERTY ${_property}) + set_property(${_type} ${_target} PROPERTY ${_property} "${_propertyValue}") + endif() + endforeach() +endfunction() + +function (cotire_get_target_usage_requirements _target _targetRequirementsVar) + set (_targetRequirements "") + get_target_property(_librariesToProcess ${_target} LINK_LIBRARIES) + while (_librariesToProcess) + # remove from head + list (GET _librariesToProcess 0 _library) + list (REMOVE_AT _librariesToProcess 0) + if (TARGET ${_library}) + list (FIND _targetRequirements ${_library} _index) + if (_index LESS 0) + list (APPEND _targetRequirements ${_library}) + # process transitive libraries + get_target_property(_libraries ${_library} INTERFACE_LINK_LIBRARIES) + if (_libraries) + list (APPEND _librariesToProcess ${_libraries}) + list (REMOVE_DUPLICATES _librariesToProcess) + endif() + endif() + endif() + endwhile() + set (${_targetRequirementsVar} ${_targetRequirements} PARENT_SCOPE) +endfunction() + +function (cotire_filter_compile_flags _language _flagFilter _matchedOptionsVar _unmatchedOptionsVar) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + set (_flagPrefix "[/-]") + else() + set (_flagPrefix "--?") + endif() + set (_optionFlag "") + set (_matchedOptions "") + set (_unmatchedOptions "") + foreach (_compileFlag ${ARGN}) + if (_compileFlag) + if (_optionFlag AND NOT "${_compileFlag}" MATCHES "^${_flagPrefix}") + # option with separate argument + list (APPEND _matchedOptions "${_compileFlag}") + set (_optionFlag "") + elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})$") + # remember option + set (_optionFlag "${CMAKE_MATCH_2}") + elseif ("${_compileFlag}" MATCHES "^(${_flagPrefix})(${_flagFilter})(.+)$") + # option with joined argument + list (APPEND _matchedOptions "${CMAKE_MATCH_3}") + set (_optionFlag "") + else() + # flush remembered option + if (_optionFlag) + list (APPEND _matchedOptions "${_optionFlag}") + set (_optionFlag "") + endif() + # add to unfiltered options + list (APPEND _unmatchedOptions "${_compileFlag}") + endif() + endif() + endforeach() + if (_optionFlag) + list (APPEND _matchedOptions "${_optionFlag}") + endif() + if (COTIRE_DEBUG AND _matchedOptions) + message (STATUS "Filter ${_flagFilter} matched: ${_matchedOptions}") + endif() + if (COTIRE_DEBUG AND _unmatchedOptions) + message (STATUS "Filter ${_flagFilter} unmatched: ${_unmatchedOptions}") + endif() + set (${_matchedOptionsVar} ${_matchedOptions} PARENT_SCOPE) + set (${_unmatchedOptionsVar} ${_unmatchedOptions} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compile_flags _config _language _target _flagsVar) + string (TOUPPER "${_config}" _upperConfig) + # collect options from CMake language variables + set (_compileFlags "") + if (CMAKE_${_language}_FLAGS) + set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS}") + endif() + if (CMAKE_${_language}_FLAGS_${_upperConfig}) + set (_compileFlags "${_compileFlags} ${CMAKE_${_language}_FLAGS_${_upperConfig}}") + endif() + if (_target) + # add target compile flags + get_target_property(_targetflags ${_target} COMPILE_FLAGS) + if (_targetflags) + set (_compileFlags "${_compileFlags} ${_targetflags}") + endif() + endif() + if (UNIX) + separate_arguments(_compileFlags UNIX_COMMAND "${_compileFlags}") + elseif(WIN32) + separate_arguments(_compileFlags WINDOWS_COMMAND "${_compileFlags}") + else() + separate_arguments(_compileFlags) + endif() + # target compile options + if (_target) + get_target_property(_targetOptions ${_target} COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endif() + # interface compile options from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetOptions ${_linkedTarget} INTERFACE_COMPILE_OPTIONS) + if (_targetOptions) + list (APPEND _compileFlags ${_targetOptions}) + endif() + endforeach() + endif() + # handle language standard properties + if (_target) + get_target_property(_targetLanguageStandard ${_target} ${_language}_STANDARD) + get_target_property(_targetLanguageExtensions ${_target} ${_language}_EXTENSIONS) + get_target_property(_targetLanguageStandardRequired ${_target} ${_language}_STANDARD_REQUIRED) + if (_targetLanguageExtensions) + if (CMAKE_${_language}${_targetLanguageExtensions}_EXTENSION_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageExtensions}_EXTENSION_COMPILE_OPTION}") + endif() + elseif (_targetLanguageStandard) + if (_targetLanguageStandardRequired) + if (CMAKE_${_language}${_targetLanguageStandard}_STANDARD_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_STANDARD_COMPILE_OPTION}") + endif() + else() + if (CMAKE_${_language}${_targetLanguageStandard}_EXTENSION_COMPILE_OPTION) + list (APPEND _compileFlags "${CMAKE_${_language}${_targetLanguageStandard}_EXTENSION_COMPILE_OPTION}") + endif() + endif() + endif() + endif() + # handle the POSITION_INDEPENDENT_CODE target property + if (_target) + get_target_property(_targetPIC ${_target} POSITION_INDEPENDENT_CODE) + if (_targetPIC) + get_target_property(_targetType ${_target} TYPE) + if (_targetType STREQUAL "EXECUTABLE" AND CMAKE_${_language}_COMPILE_OPTIONS_PIE) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIE}") + elseif (CMAKE_${_language}_COMPILE_OPTIONS_PIC) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_PIC}") + endif() + endif() + endif() + # handle visibility target properties + if (_target) + get_target_property(_targetVisibility ${_target} ${_language}_VISIBILITY_PRESET) + if (_targetVisibility AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY}${_targetVisibility}") + endif() + get_target_property(_targetVisibilityInlines ${_target} VISIBILITY_INLINES_HIDDEN) + if (_targetVisibilityInlines AND CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN) + list (APPEND _compileFlags "${CMAKE_${_language}_COMPILE_OPTIONS_VISIBILITY_INLINES_HIDDEN}") + endif() + endif() + # platform specific flags + if (APPLE) + get_target_property(_architectures ${_target} OSX_ARCHITECTURES_${_upperConfig}) + if (NOT _architectures) + get_target_property(_architectures ${_target} OSX_ARCHITECTURES) + endif() + if (_architectures) + foreach (_arch ${_architectures}) + list (APPEND _compileFlags "-arch" "${_arch}") + endforeach() + endif() + if (CMAKE_OSX_SYSROOT) + if (CMAKE_${_language}_SYSROOT_FLAG) + list (APPEND _compileFlags "${CMAKE_${_language}_SYSROOT_FLAG}" "${CMAKE_OSX_SYSROOT}") + else() + list (APPEND _compileFlags "-isysroot" "${CMAKE_OSX_SYSROOT}") + endif() + endif() + if (CMAKE_OSX_DEPLOYMENT_TARGET) + if (CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG) + list (APPEND _compileFlags "${CMAKE_${_language}_OSX_DEPLOYMENT_TARGET_FLAG}${CMAKE_OSX_DEPLOYMENT_TARGET}") + else() + list (APPEND _compileFlags "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + endif() + endif() + if (COTIRE_DEBUG AND _compileFlags) + message (STATUS "Target ${_target} compile flags: ${_compileFlags}") + endif() + set (${_flagsVar} ${_compileFlags} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_include_directories _config _language _target _includeDirsVar _systemIncludeDirsVar) + set (_includeDirs "") + set (_systemIncludeDirs "") + # default include dirs + if (CMAKE_INCLUDE_CURRENT_DIR) + list (APPEND _includeDirs "${CMAKE_CURRENT_BINARY_DIR}") + list (APPEND _includeDirs "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + # parse additional include directories from target compile flags + if (CMAKE_INCLUDE_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_dirs "") + cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) + if (_dirs) + list (APPEND _includeDirs ${_dirs}) + endif() + endif() + endif() + # parse additional system include directories from target compile flags + if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_dirs "") + cotire_filter_compile_flags("${_language}" "${_includeFlag}" _dirs _ignore ${_targetFlags}) + if (_dirs) + list (APPEND _systemIncludeDirs ${_dirs}) + endif() + endif() + endif() + # target include directories + get_directory_property(_dirs DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" INCLUDE_DIRECTORIES) + if (_target) + get_target_property(_targetDirs ${_target} INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _dirs ${_targetDirs}) + endif() + get_target_property(_targetDirs ${_target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endif() + # interface include directories from linked library targets + if (_target) + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _dirs ${_targetDirs}) + endif() + get_target_property(_targetDirs ${_linkedTarget} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + if (_targetDirs) + list (APPEND _systemIncludeDirs ${_targetDirs}) + endif() + endforeach() + endif() + if (dirs) + list (REMOVE_DUPLICATES _dirs) + endif() + list (LENGTH _includeDirs _projectInsertIndex) + foreach (_dir ${_dirs}) + if (CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE) + cotire_check_is_path_relative_to("${_dir}" _isRelative "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") + if (_isRelative) + list (LENGTH _includeDirs _len) + if (_len EQUAL _projectInsertIndex) + list (APPEND _includeDirs "${_dir}") + else() + list (INSERT _includeDirs _projectInsertIndex "${_dir}") + endif() + math (EXPR _projectInsertIndex "${_projectInsertIndex} + 1") + else() + list (APPEND _includeDirs "${_dir}") + endif() + else() + list (APPEND _includeDirs "${_dir}") + endif() + endforeach() + list (REMOVE_DUPLICATES _includeDirs) + list (REMOVE_DUPLICATES _systemIncludeDirs) + if (CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES) + list (REMOVE_ITEM _includeDirs ${CMAKE_${_language}_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() + if (COTIRE_DEBUG AND _includeDirs) + message (STATUS "Target ${_target} include dirs: ${_includeDirs}") + endif() + set (${_includeDirsVar} ${_includeDirs} PARENT_SCOPE) + if (COTIRE_DEBUG AND _systemIncludeDirs) + message (STATUS "Target ${_target} system include dirs: ${_systemIncludeDirs}") + endif() + set (${_systemIncludeDirsVar} ${_systemIncludeDirs} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_export_symbol _target _exportSymbolVar) + set (_exportSymbol "") + get_target_property(_targetType ${_target} TYPE) + get_target_property(_enableExports ${_target} ENABLE_EXPORTS) + if (_targetType MATCHES "(SHARED|MODULE)_LIBRARY" OR + (_targetType STREQUAL "EXECUTABLE" AND _enableExports)) + get_target_property(_exportSymbol ${_target} DEFINE_SYMBOL) + if (NOT _exportSymbol) + set (_exportSymbol "${_target}_EXPORTS") + endif() + string (MAKE_C_IDENTIFIER "${_exportSymbol}" _exportSymbol) + endif() + set (${_exportSymbolVar} ${_exportSymbol} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compile_definitions _config _language _target _definitionsVar) + string (TOUPPER "${_config}" _upperConfig) + set (_configDefinitions "") + # CMAKE_INTDIR for multi-configuration build systems + if (NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".") + list (APPEND _configDefinitions "CMAKE_INTDIR=\"${_config}\"") + endif() + # target export define symbol + cotire_get_target_export_symbol("${_target}" _defineSymbol) + if (_defineSymbol) + list (APPEND _configDefinitions "${_defineSymbol}") + endif() + # directory compile definitions + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + get_directory_property(_definitions DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + # target compile definitions + get_target_property(_definitions ${_target} COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + get_target_property(_definitions ${_target} COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + # interface compile definitions from linked library targets + set (_linkedTargets "") + cotire_get_target_usage_requirements(${_target} _linkedTargets) + foreach (_linkedTarget ${_linkedTargets}) + get_target_property(_definitions ${_linkedTarget} INTERFACE_COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + endforeach() + # parse additional compile definitions from target compile flags + # and don't look at directory compile definitions, which we already handled + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + cotire_filter_compile_flags("${_language}" "D" _definitions _ignore ${_targetFlags}) + if (_definitions) + list (APPEND _configDefinitions ${_definitions}) + endif() + list (REMOVE_DUPLICATES _configDefinitions) + if (COTIRE_DEBUG AND _configDefinitions) + message (STATUS "Target ${_target} compile definitions: ${_configDefinitions}") + endif() + set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_target_compiler_flags _config _language _target _compilerFlagsVar) + # parse target compile flags omitting compile definitions and include directives + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + set (_flagFilter "D") + if (CMAKE_INCLUDE_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_flagFilter "${_flagFilter}|${_includeFlag}") + endif() + endif() + if (CMAKE_INCLUDE_SYSTEM_FLAG_${_language}) + string (STRIP "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" _includeFlag) + string (REGEX REPLACE "^[-/]+" "" _includeFlag "${_includeFlag}") + if (_includeFlag) + set (_flagFilter "${_flagFilter}|${_includeFlag}") + endif() + endif() + set (_compilerFlags "") + cotire_filter_compile_flags("${_language}" "${_flagFilter}" _ignore _compilerFlags ${_targetFlags}) + if (COTIRE_DEBUG AND _compilerFlags) + message (STATUS "Target ${_target} compiler flags: ${_compilerFlags}") + endif() + set (${_compilerFlagsVar} ${_compilerFlags} PARENT_SCOPE) +endfunction() + +function (cotire_add_sys_root_paths _pathsVar) + if (APPLE) + if (CMAKE_OSX_SYSROOT AND CMAKE_${_language}_HAS_ISYSROOT) + foreach (_path IN LISTS ${_pathsVar}) + if (IS_ABSOLUTE "${_path}") + get_filename_component(_path "${CMAKE_OSX_SYSROOT}/${_path}" ABSOLUTE) + if (EXISTS "${_path}") + list (APPEND ${_pathsVar} "${_path}") + endif() + endif() + endforeach() + endif() + endif() + set (${_pathsVar} ${${_pathsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_extra_properties _sourceFile _pattern _resultVar) + set (_extraProperties ${ARGN}) + set (_result "") + if (_extraProperties) + list (FIND _extraProperties "${_sourceFile}" _index) + if (_index GREATER -1) + math (EXPR _index "${_index} + 1") + list (LENGTH _extraProperties _len) + math (EXPR _len "${_len} - 1") + foreach (_index RANGE ${_index} ${_len}) + list (GET _extraProperties ${_index} _value) + if (_value MATCHES "${_pattern}") + list (APPEND _result "${_value}") + else() + break() + endif() + endforeach() + endif() + endif() + set (${_resultVar} ${_result} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_compile_definitions _config _language _sourceFile _definitionsVar) + set (_compileDefinitions "") + if (NOT CMAKE_SCRIPT_MODE_FILE) + string (TOUPPER "${_config}" _upperConfig) + get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + get_source_file_property(_definitions "${_sourceFile}" COMPILE_DEFINITIONS_${_upperConfig}) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + endif() + cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+(=.*)?$" _definitions ${ARGN}) + if (_definitions) + list (APPEND _compileDefinitions ${_definitions}) + endif() + if (COTIRE_DEBUG AND _compileDefinitions) + message (STATUS "Source ${_sourceFile} compile definitions: ${_compileDefinitions}") + endif() + set (${_definitionsVar} ${_compileDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_files_compile_definitions _config _language _definitionsVar) + set (_configDefinitions "") + foreach (_sourceFile ${ARGN}) + cotire_get_source_compile_definitions("${_config}" "${_language}" "${_sourceFile}" _sourceDefinitions) + if (_sourceDefinitions) + list (APPEND _configDefinitions "${_sourceFile}" ${_sourceDefinitions} "-") + endif() + endforeach() + set (${_definitionsVar} ${_configDefinitions} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_undefs _sourceFile _property _sourceUndefsVar) + set (_sourceUndefs "") + if (NOT CMAKE_SCRIPT_MODE_FILE) + get_source_file_property(_undefs "${_sourceFile}" ${_property}) + if (_undefs) + list (APPEND _sourceUndefs ${_undefs}) + endif() + endif() + cotire_get_source_extra_properties("${_sourceFile}" "^[a-zA-Z0-9_]+$" _undefs ${ARGN}) + if (_undefs) + list (APPEND _sourceUndefs ${_undefs}) + endif() + if (COTIRE_DEBUG AND _sourceUndefs) + message (STATUS "Source ${_sourceFile} ${_property} undefs: ${_sourceUndefs}") + endif() + set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) +endfunction() + +function (cotire_get_source_files_undefs _property _sourceUndefsVar) + set (_sourceUndefs "") + foreach (_sourceFile ${ARGN}) + cotire_get_source_undefs("${_sourceFile}" ${_property} _undefs) + if (_undefs) + list (APPEND _sourceUndefs "${_sourceFile}" ${_undefs} "-") + endif() + endforeach() + set (${_sourceUndefsVar} ${_sourceUndefs} PARENT_SCOPE) +endfunction() + +macro (cotire_set_cmd_to_prologue _cmdVar) + set (${_cmdVar} "${CMAKE_COMMAND}") + if (COTIRE_DEBUG) + list (APPEND ${_cmdVar} "--warn-uninitialized") + endif() + list (APPEND ${_cmdVar} "-DCOTIRE_BUILD_TYPE:STRING=$") + if (COTIRE_VERBOSE) + list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=ON") + elseif("${CMAKE_GENERATOR}" MATCHES "Makefiles") + list (APPEND ${_cmdVar} "-DCOTIRE_VERBOSE:BOOL=$(VERBOSE)") + endif() +endmacro() + +function (cotire_init_compile_cmd _cmdVar _language _compilerExe _compilerArg1) + if (NOT _compilerExe) + set (_compilerExe "${CMAKE_${_language}_COMPILER}") + endif() + if (NOT _compilerArg1) + set (_compilerArg1 ${CMAKE_${_language}_COMPILER_ARG1}) + endif() + string (STRIP "${_compilerArg1}" _compilerArg1) + set (${_cmdVar} "${_compilerExe}" ${_compilerArg1} PARENT_SCOPE) +endfunction() + +macro (cotire_add_definitions_to_cmd _cmdVar _language) + foreach (_definition ${ARGN}) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + list (APPEND ${_cmdVar} "/D${_definition}") + else() + list (APPEND ${_cmdVar} "-D${_definition}") + endif() + endforeach() +endmacro() + +function (cotire_add_includes_to_cmd _cmdVar _language _includesVar _systemIncludesVar) + set (_includeDirs ${${_includesVar}} ${${_systemIncludesVar}}) + if (_includeDirs) + list (REMOVE_DUPLICATES _includeDirs) + foreach (_include ${_includeDirs}) + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + file (TO_NATIVE_PATH "${_include}" _include) + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_${_language}_SEP}${_include}") + else() + set (_index -1) + if ("${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}" MATCHES ".+") + list (FIND ${_systemIncludesVar} "${_include}" _index) + endif() + if (_index GREATER -1) + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_SYSTEM_FLAG_${_language}}${_include}") + else() + list (APPEND ${_cmdVar} "${CMAKE_INCLUDE_FLAG_${_language}}${CMAKE_INCLUDE_FLAG_${_language}_SEP}${_include}") + endif() + endif() + endforeach() + endif() + set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) +endfunction() + +function (cotire_add_frameworks_to_cmd _cmdVar _language _includesVar _systemIncludesVar) + if (APPLE) + set (_frameworkDirs "") + foreach (_include ${${_includesVar}}) + if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") + get_filename_component(_frameworkDir "${_include}" DIRECTORY) + list (APPEND _frameworkDirs "${_frameworkDir}") + endif() + endforeach() + set (_systemFrameworkDirs "") + foreach (_include ${${_systemIncludesVar}}) + if (IS_ABSOLUTE "${_include}" AND _include MATCHES "\\.framework$") + get_filename_component(_frameworkDir "${_include}" DIRECTORY) + list (APPEND _systemFrameworkDirs "${_frameworkDir}") + endif() + endforeach() + if (_systemFrameworkDirs) + list (APPEND _frameworkDirs ${_systemFrameworkDirs}) + endif() + if (_frameworkDirs) + list (REMOVE_DUPLICATES _frameworkDirs) + foreach (_frameworkDir ${_frameworkDirs}) + set (_index -1) + if ("${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}" MATCHES ".+") + list (FIND _systemFrameworkDirs "${_frameworkDir}" _index) + endif() + if (_index GREATER -1) + list (APPEND ${_cmdVar} "${CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") + else() + list (APPEND ${_cmdVar} "${CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG}${_frameworkDir}") + endif() + endforeach() + endif() + endif() + set (${_cmdVar} ${${_cmdVar}} PARENT_SCOPE) +endfunction() + +macro (cotire_add_compile_flags_to_cmd _cmdVar) + foreach (_flag ${ARGN}) + list (APPEND ${_cmdVar} "${_flag}") + endforeach() +endmacro() + +function (cotire_check_file_up_to_date _fileIsUpToDateVar _file) + if (EXISTS "${_file}") + set (_triggerFile "") + foreach (_dependencyFile ${ARGN}) + if (EXISTS "${_dependencyFile}") + # IS_NEWER_THAN returns TRUE if both files have the same timestamp + # thus we do the comparison in both directions to exclude ties + if ("${_dependencyFile}" IS_NEWER_THAN "${_file}" AND + NOT "${_file}" IS_NEWER_THAN "${_dependencyFile}") + set (_triggerFile "${_dependencyFile}") + break() + endif() + endif() + endforeach() + if (_triggerFile) + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} update triggered by ${_triggerFile} change.") + endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) + else() + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} is up-to-date.") + endif() + set (${_fileIsUpToDateVar} TRUE PARENT_SCOPE) + endif() + else() + if (COTIRE_VERBOSE) + get_filename_component(_fileName "${_file}" NAME) + message (STATUS "${_fileName} does not exist yet.") + endif() + set (${_fileIsUpToDateVar} FALSE PARENT_SCOPE) + endif() +endfunction() + +macro (cotire_find_closest_relative_path _headerFile _includeDirs _relPathVar) + set (${_relPathVar} "") + foreach (_includeDir ${_includeDirs}) + if (IS_DIRECTORY "${_includeDir}") + file (RELATIVE_PATH _relPath "${_includeDir}" "${_headerFile}") + if (NOT IS_ABSOLUTE "${_relPath}" AND NOT "${_relPath}" MATCHES "^\\.\\.") + string (LENGTH "${${_relPathVar}}" _closestLen) + string (LENGTH "${_relPath}" _relLen) + if (_closestLen EQUAL 0 OR _relLen LESS _closestLen) + set (${_relPathVar} "${_relPath}") + endif() + endif() + elseif ("${_includeDir}" STREQUAL "${_headerFile}") + # if path matches exactly, return short non-empty string + set (${_relPathVar} "1") + break() + endif() + endforeach() +endmacro() + +macro (cotire_check_header_file_location _headerFile _insideIncludeDirs _outsideIncludeDirs _headerIsInside) + # check header path against ignored and honored include directories + cotire_find_closest_relative_path("${_headerFile}" "${_insideIncludeDirs}" _insideRelPath) + if (_insideRelPath) + # header is inside, but could be become outside if there is a shorter outside match + cotire_find_closest_relative_path("${_headerFile}" "${_outsideIncludeDirs}" _outsideRelPath) + if (_outsideRelPath) + string (LENGTH "${_insideRelPath}" _insideRelPathLen) + string (LENGTH "${_outsideRelPath}" _outsideRelPathLen) + if (_outsideRelPathLen LESS _insideRelPathLen) + set (${_headerIsInside} FALSE) + else() + set (${_headerIsInside} TRUE) + endif() + else() + set (${_headerIsInside} TRUE) + endif() + else() + # header is outside + set (${_headerIsInside} FALSE) + endif() +endmacro() + +macro (cotire_check_ignore_header_file_path _headerFile _headerIsIgnoredVar) + if (NOT EXISTS "${_headerFile}") + set (${_headerIsIgnoredVar} TRUE) + elseif (IS_DIRECTORY "${_headerFile}") + set (${_headerIsIgnoredVar} TRUE) + elseif ("${_headerFile}" MATCHES "\\.\\.|[_-]fixed" AND "${_headerFile}" MATCHES "\\.h$") + # heuristic: ignore C headers with embedded parent directory references or "-fixed" or "_fixed" in path + # these often stem from using GCC #include_next tricks, which may break the precompiled header compilation + # with the error message "error: no include path in which to search for header.h" + set (${_headerIsIgnoredVar} TRUE) + else() + set (${_headerIsIgnoredVar} FALSE) + endif() +endmacro() + +macro (cotire_check_ignore_header_file_ext _headerFile _ignoreExtensionsVar _headerIsIgnoredVar) + # check header file extension + cotire_get_source_file_extension("${_headerFile}" _headerFileExt) + set (${_headerIsIgnoredVar} FALSE) + if (_headerFileExt) + list (FIND ${_ignoreExtensionsVar} "${_headerFileExt}" _index) + if (_index GREATER -1) + set (${_headerIsIgnoredVar} TRUE) + endif() + endif() +endmacro() + +macro (cotire_parse_line _line _headerFileVar _headerDepthVar) + if (MSVC) + # cl.exe /showIncludes output looks different depending on the language pack used, e.g.: + # English: "Note: including file: C:\directory\file" + # German: "Hinweis: Einlesen der Datei: C:\directory\file" + # We use a very general regular expression, relying on the presence of the : characters + if (_line MATCHES "( +)([a-zA-Z]:[^:]+)$") + # Visual Studio compiler output + string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) + get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" ABSOLUTE) + else() + set (${_headerFileVar} "") + set (${_headerDepthVar} 0) + endif() + else() + if (_line MATCHES "^(\\.+) (.*)$") + # GCC like output + string (LENGTH "${CMAKE_MATCH_1}" ${_headerDepthVar}) + if (IS_ABSOLUTE "${CMAKE_MATCH_2}") + set (${_headerFileVar} "${CMAKE_MATCH_2}") + else() + get_filename_component(${_headerFileVar} "${CMAKE_MATCH_2}" REALPATH) + endif() + else() + set (${_headerFileVar} "") + set (${_headerDepthVar} 0) + endif() + endif() +endmacro() + +function (cotire_parse_includes _language _scanOutput _ignoredIncludeDirs _honoredIncludeDirs _ignoredExtensions _selectedIncludesVar _unparsedLinesVar) + if (WIN32) + # prevent CMake macro invocation errors due to backslash characters in Windows paths + string (REPLACE "\\" "/" _scanOutput "${_scanOutput}") + endif() + # canonize slashes + string (REPLACE "//" "/" _scanOutput "${_scanOutput}") + # prevent semicolon from being interpreted as a line separator + string (REPLACE ";" "\\;" _scanOutput "${_scanOutput}") + # then separate lines + string (REGEX REPLACE "\n" ";" _scanOutput "${_scanOutput}") + list (LENGTH _scanOutput _len) + # remove duplicate lines to speed up parsing + list (REMOVE_DUPLICATES _scanOutput) + list (LENGTH _scanOutput _uniqueLen) + if (COTIRE_VERBOSE OR COTIRE_DEBUG) + message (STATUS "Scanning ${_uniqueLen} unique lines of ${_len} for includes") + if (_ignoredExtensions) + message (STATUS "Ignored extensions: ${_ignoredExtensions}") + endif() + if (_ignoredIncludeDirs) + message (STATUS "Ignored paths: ${_ignoredIncludeDirs}") + endif() + if (_honoredIncludeDirs) + message (STATUS "Included paths: ${_honoredIncludeDirs}") + endif() + endif() + set (_sourceFiles ${ARGN}) + set (_selectedIncludes "") + set (_unparsedLines "") + # stack keeps track of inside/outside project status of processed header files + set (_headerIsInsideStack "") + foreach (_line IN LISTS _scanOutput) + if (_line) + cotire_parse_line("${_line}" _headerFile _headerDepth) + if (_headerFile) + cotire_check_header_file_location("${_headerFile}" "${_ignoredIncludeDirs}" "${_honoredIncludeDirs}" _headerIsInside) + if (COTIRE_DEBUG) + message (STATUS "${_headerDepth}: ${_headerFile} ${_headerIsInside}") + endif() + # update stack + list (LENGTH _headerIsInsideStack _stackLen) + if (_headerDepth GREATER _stackLen) + math (EXPR _stackLen "${_stackLen} + 1") + foreach (_index RANGE ${_stackLen} ${_headerDepth}) + list (APPEND _headerIsInsideStack ${_headerIsInside}) + endforeach() + else() + foreach (_index RANGE ${_headerDepth} ${_stackLen}) + list (REMOVE_AT _headerIsInsideStack -1) + endforeach() + list (APPEND _headerIsInsideStack ${_headerIsInside}) + endif() + if (COTIRE_DEBUG) + message (STATUS "${_headerIsInsideStack}") + endif() + # header is a candidate if it is outside project + if (NOT _headerIsInside) + # get parent header file's inside/outside status + if (_headerDepth GREATER 1) + math (EXPR _index "${_headerDepth} - 2") + list (GET _headerIsInsideStack ${_index} _parentHeaderIsInside) + else() + set (_parentHeaderIsInside TRUE) + endif() + # select header file if parent header file is inside project + # (e.g., a project header file that includes a standard header file) + if (_parentHeaderIsInside) + cotire_check_ignore_header_file_path("${_headerFile}" _headerIsIgnored) + if (NOT _headerIsIgnored) + cotire_check_ignore_header_file_ext("${_headerFile}" _ignoredExtensions _headerIsIgnored) + if (NOT _headerIsIgnored) + list (APPEND _selectedIncludes "${_headerFile}") + else() + # fix header's inside status on stack, it is ignored by extension now + list (REMOVE_AT _headerIsInsideStack -1) + list (APPEND _headerIsInsideStack TRUE) + endif() + endif() + if (COTIRE_DEBUG) + message (STATUS "${_headerFile} ${_ignoredExtensions} ${_headerIsIgnored}") + endif() + endif() + endif() + else() + if (MSVC) + # for cl.exe do not keep unparsed lines which solely consist of a source file name + string (FIND "${_sourceFiles}" "${_line}" _index) + if (_index LESS 0) + list (APPEND _unparsedLines "${_line}") + endif() + else() + list (APPEND _unparsedLines "${_line}") + endif() + endif() + endif() + endforeach() + list (REMOVE_DUPLICATES _selectedIncludes) + set (${_selectedIncludesVar} ${_selectedIncludes} PARENT_SCOPE) + set (${_unparsedLinesVar} ${_unparsedLines} PARENT_SCOPE) +endfunction() + +function (cotire_scan_includes _includesVar) + set(_options "") + set(_oneValueArgs COMPILER_ID COMPILER_EXECUTABLE COMPILER_VERSION LANGUAGE UNPARSED_LINES) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES + IGNORE_PATH INCLUDE_PATH IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + if (NOT _option_LANGUAGE) + set (_option_LANGUAGE "CXX") + endif() + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") + cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) + cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_makedep_flags("${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" _cmd) + # only consider existing source files for scanning + set (_existingSourceFiles "") + foreach (_sourceFile ${_sourceFiles}) + if (EXISTS "${_sourceFile}") + list (APPEND _existingSourceFiles "${_sourceFile}") + endif() + endforeach() + if (NOT _existingSourceFiles) + set (${_includesVar} "" PARENT_SCOPE) + return() + endif() + list (APPEND _cmd ${_existingSourceFiles}) + if (COTIRE_VERBOSE) + message (STATUS "execute_process: ${_cmd}") + endif() + if (_option_COMPILER_ID MATCHES "MSVC") + # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared + unset (ENV{VS_UNICODE_OUTPUT}) + endif() + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result + OUTPUT_QUIET + ERROR_VARIABLE _output) + if (_result) + message (STATUS "Result ${_result} scanning includes of ${_existingSourceFiles}.") + endif() + cotire_parse_includes( + "${_option_LANGUAGE}" "${_output}" + "${_option_IGNORE_PATH}" "${_option_INCLUDE_PATH}" + "${_option_IGNORE_EXTENSIONS}" + _includes _unparsedLines + ${_sourceFiles}) + if (_option_INCLUDE_PRIORITY_PATH) + set (_sortedIncludes "") + foreach (_priorityPath ${_option_INCLUDE_PRIORITY_PATH}) + foreach (_include ${_includes}) + string (FIND ${_include} ${_priorityPath} _position) + if (_position GREATER -1) + list (APPEND _sortedIncludes ${_include}) + endif() + endforeach() + endforeach() + if (_sortedIncludes) + list (INSERT _includes 0 ${_sortedIncludes}) + list (REMOVE_DUPLICATES _includes) + endif() + endif() + set (${_includesVar} ${_includes} PARENT_SCOPE) + if (_option_UNPARSED_LINES) + set (${_option_UNPARSED_LINES} ${_unparsedLines} PARENT_SCOPE) + endif() +endfunction() + +macro (cotire_append_undefs _contentsVar) + set (_undefs ${ARGN}) + if (_undefs) + list (REMOVE_DUPLICATES _undefs) + foreach (_definition ${_undefs}) + list (APPEND ${_contentsVar} "#undef ${_definition}") + endforeach() + endif() +endmacro() + +macro (cotire_comment_str _language _commentText _commentVar) + if ("${_language}" STREQUAL "CMAKE") + set (${_commentVar} "# ${_commentText}") + else() + set (${_commentVar} "/* ${_commentText} */") + endif() +endmacro() + +function (cotire_write_file _language _file _contents _force) + get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) + cotire_comment_str("${_language}" "${_moduleName} ${COTIRE_CMAKE_MODULE_VERSION} generated file" _header1) + cotire_comment_str("${_language}" "${_file}" _header2) + set (_contents "${_header1}\n${_header2}\n${_contents}") + if (COTIRE_DEBUG) + message (STATUS "${_contents}") + endif() + if (_force OR NOT EXISTS "${_file}") + file (WRITE "${_file}" "${_contents}") + else() + file (READ "${_file}" _oldContents) + if (NOT "${_oldContents}" STREQUAL "${_contents}") + file (WRITE "${_file}" "${_contents}") + else() + if (COTIRE_DEBUG) + message (STATUS "${_file} unchanged") + endif() + endif() + endif() +endfunction() + +function (cotire_generate_unity_source _unityFile) + set(_options "") + set(_oneValueArgs LANGUAGE) + set(_multiValueArgs + DEPENDS SOURCES_COMPILE_DEFINITIONS + PRE_UNDEFS SOURCES_PRE_UNDEFS POST_UNDEFS SOURCES_POST_UNDEFS PROLOGUE EPILOGUE) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (_option_DEPENDS) + cotire_check_file_up_to_date(_unityFileIsUpToDate "${_unityFile}" ${_option_DEPENDS}) + if (_unityFileIsUpToDate) + return() + endif() + endif() + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + if (NOT _option_PRE_UNDEFS) + set (_option_PRE_UNDEFS "") + endif() + if (NOT _option_SOURCES_PRE_UNDEFS) + set (_option_SOURCES_PRE_UNDEFS "") + endif() + if (NOT _option_POST_UNDEFS) + set (_option_POST_UNDEFS "") + endif() + if (NOT _option_SOURCES_POST_UNDEFS) + set (_option_SOURCES_POST_UNDEFS "") + endif() + set (_contents "") + if (_option_PROLOGUE) + list (APPEND _contents ${_option_PROLOGUE}) + endif() + if (_option_LANGUAGE AND _sourceFiles) + if ("${_option_LANGUAGE}" STREQUAL "CXX") + list (APPEND _contents "#ifdef __cplusplus") + elseif ("${_option_LANGUAGE}" STREQUAL "C") + list (APPEND _contents "#ifndef __cplusplus") + endif() + endif() + set (_compileUndefinitions "") + foreach (_sourceFile ${_sourceFiles}) + cotire_get_source_compile_definitions( + "${_option_CONFIGURATION}" "${_option_LANGUAGE}" "${_sourceFile}" _compileDefinitions + ${_option_SOURCES_COMPILE_DEFINITIONS}) + cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_PRE_UNDEFS _sourcePreUndefs ${_option_SOURCES_PRE_UNDEFS}) + cotire_get_source_undefs("${_sourceFile}" COTIRE_UNITY_SOURCE_POST_UNDEFS _sourcePostUndefs ${_option_SOURCES_POST_UNDEFS}) + if (_option_PRE_UNDEFS) + list (APPEND _compileUndefinitions ${_option_PRE_UNDEFS}) + endif() + if (_sourcePreUndefs) + list (APPEND _compileUndefinitions ${_sourcePreUndefs}) + endif() + if (_compileUndefinitions) + cotire_append_undefs(_contents ${_compileUndefinitions}) + set (_compileUndefinitions "") + endif() + if (_sourcePostUndefs) + list (APPEND _compileUndefinitions ${_sourcePostUndefs}) + endif() + if (_option_POST_UNDEFS) + list (APPEND _compileUndefinitions ${_option_POST_UNDEFS}) + endif() + foreach (_definition ${_compileDefinitions}) + if (_definition MATCHES "^([a-zA-Z0-9_]+)=(.+)$") + list (APPEND _contents "#define ${CMAKE_MATCH_1} ${CMAKE_MATCH_2}") + list (INSERT _compileUndefinitions 0 "${CMAKE_MATCH_1}") + else() + list (APPEND _contents "#define ${_definition}") + list (INSERT _compileUndefinitions 0 "${_definition}") + endif() + endforeach() + # use absolute path as source file location + get_filename_component(_sourceFileLocation "${_sourceFile}" ABSOLUTE) + if (WIN32) + file (TO_NATIVE_PATH "${_sourceFileLocation}" _sourceFileLocation) + endif() + list (APPEND _contents "#include \"${_sourceFileLocation}\"") + endforeach() + if (_compileUndefinitions) + cotire_append_undefs(_contents ${_compileUndefinitions}) + set (_compileUndefinitions "") + endif() + if (_option_LANGUAGE AND _sourceFiles) + list (APPEND _contents "#endif") + endif() + if (_option_EPILOGUE) + list (APPEND _contents ${_option_EPILOGUE}) + endif() + list (APPEND _contents "") + string (REPLACE ";" "\n" _contents "${_contents}") + if (COTIRE_VERBOSE) + message ("${_contents}") + endif() + cotire_write_file("${_option_LANGUAGE}" "${_unityFile}" "${_contents}" TRUE) +endfunction() + +function (cotire_generate_prefix_header _prefixFile) + set(_options "") + set(_oneValueArgs LANGUAGE COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION) + set(_multiValueArgs DEPENDS COMPILE_DEFINITIONS COMPILE_FLAGS + INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES IGNORE_PATH INCLUDE_PATH + IGNORE_EXTENSIONS INCLUDE_PRIORITY_PATH) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + if (_option_DEPENDS) + cotire_check_file_up_to_date(_prefixFileIsUpToDate "${_prefixFile}" ${_option_DEPENDS}) + if (_prefixFileIsUpToDate) + # create empty log file + set (_unparsedLinesFile "${_prefixFile}.log") + file (WRITE "${_unparsedLinesFile}" "") + return() + endif() + endif() + set (_prologue "") + set (_epilogue "") + if (_option_COMPILER_ID MATCHES "Clang") + set (_prologue "#pragma clang system_header") + elseif (_option_COMPILER_ID MATCHES "GNU") + set (_prologue "#pragma GCC system_header") + elseif (_option_COMPILER_ID MATCHES "MSVC") + set (_prologue "#pragma warning(push, 0)") + set (_epilogue "#pragma warning(pop)") + elseif (_option_COMPILER_ID MATCHES "Intel") + # Intel compiler requires hdrstop pragma to stop generating PCH file + set (_epilogue "#pragma hdrstop") + endif() + set (_sourceFiles ${_option_UNPARSED_ARGUMENTS}) + cotire_scan_includes(_selectedHeaders ${_sourceFiles} + LANGUAGE "${_option_LANGUAGE}" + COMPILER_EXECUTABLE "${_option_COMPILER_EXECUTABLE}" + COMPILER_ID "${_option_COMPILER_ID}" + COMPILER_VERSION "${_option_COMPILER_VERSION}" + COMPILE_DEFINITIONS ${_option_COMPILE_DEFINITIONS} + COMPILE_FLAGS ${_option_COMPILE_FLAGS} + INCLUDE_DIRECTORIES ${_option_INCLUDE_DIRECTORIES} + SYSTEM_INCLUDE_DIRECTORIES ${_option_SYSTEM_INCLUDE_DIRECTORIES} + IGNORE_PATH ${_option_IGNORE_PATH} + INCLUDE_PATH ${_option_INCLUDE_PATH} + IGNORE_EXTENSIONS ${_option_IGNORE_EXTENSIONS} + INCLUDE_PRIORITY_PATH ${_option_INCLUDE_PRIORITY_PATH} + UNPARSED_LINES _unparsedLines) + cotire_generate_unity_source("${_prefixFile}" + PROLOGUE ${_prologue} EPILOGUE ${_epilogue} LANGUAGE "${_option_LANGUAGE}" ${_selectedHeaders}) + set (_unparsedLinesFile "${_prefixFile}.log") + if (_unparsedLines) + if (COTIRE_VERBOSE OR NOT _selectedHeaders) + list (LENGTH _unparsedLines _skippedLineCount) + message (STATUS "${_skippedLineCount} line(s) skipped, see ${_unparsedLinesFile}") + endif() + string (REPLACE ";" "\n" _unparsedLines "${_unparsedLines}") + endif() + file (WRITE "${_unparsedLinesFile}" "${_unparsedLines}") +endfunction() + +function (cotire_add_makedep_flags _language _compilerID _compilerVersion _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + # cl.exe options used + # /nologo suppresses display of sign-on banner + # /TC treat all files named on the command line as C source files + # /TP treat all files named on the command line as C++ source files + # /EP preprocess to stdout without #line directives + # /showIncludes list include files + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /showIncludes) + else() + # return as a flag string + set (_flags "${_sourceFileType${_language}} /EP /showIncludes") + endif() + elseif (_compilerID MATCHES "GNU") + # GCC options used + # -H print the name of each header file used + # -E invoke preprocessor + # -fdirectives-only do not expand macros, requires GCC >= 4.3 + if (_flags) + # append to list + list (APPEND _flags -H -E) + if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") + list (APPEND _flags "-fdirectives-only") + endif() + else() + # return as a flag string + set (_flags "-H -E") + if (NOT "${_compilerVersion}" VERSION_LESS "4.3.0") + set (_flags "${_flags} -fdirectives-only") + endif() + endif() + elseif (_compilerID MATCHES "Clang") + # Clang options used + # -H print the name of each header file used + # -E invoke preprocessor + # -fno-color-diagnostics don't prints diagnostics in color + if (_flags) + # append to list + list (APPEND _flags -H -E -fno-color-diagnostics) + else() + # return as a flag string + set (_flags "-H -E -fno-color-diagnostics") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + # Windows Intel options used + # /nologo do not display compiler version information + # /QH display the include file order + # /EP preprocess to stdout, omitting #line directives + # /TC process all source or unrecognized file types as C source files + # /TP process all source or unrecognized file types as C++ source files + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" /EP /QH) + else() + # return as a flag string + set (_flags "${_sourceFileType${_language}} /EP /QH") + endif() + else() + # Linux / Mac OS X Intel options used + # -H print the name of each header file used + # -EP preprocess to stdout, omitting #line directives + # -Kc++ process all source or unrecognized file types as C++ source files + if (_flags) + # append to list + if ("${_language}" STREQUAL "CXX") + list (APPEND _flags -Kc++) + endif() + list (APPEND _flags -H -EP) + else() + # return as a flag string + if ("${_language}" STREQUAL "CXX") + set (_flags "-Kc++ ") + endif() + set (_flags "${_flags}-H -EP") + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_add_pch_compilation_flags _language _compilerID _compilerVersion _prefixFile _pchFile _hostFile _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) + # cl.exe options used + # /Yc creates a precompiled header file + # /Fp specifies precompiled header binary file name + # /FI forces inclusion of file + # /TC treat all files named on the command line as C source files + # /TP treat all files named on the command line as C++ source files + # /Zs syntax check only + # /Zm precompiled header memory allocation scaling factor + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" + "/Yc${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + else() + # return as a flag string + set (_flags "/Yc\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + endif() + elseif (_compilerID MATCHES "GNU|Clang") + # GCC / Clang options used + # -x specify the source language + # -c compile but do not link + # -o place output in file + # note that we cannot use -w to suppress all warnings upon pre-compiling, because turning off a warning may + # alter compile flags as a side effect (e.g., -Wwrite-string implies -fconst-strings) + set (_xLanguage_C "c-header") + set (_xLanguage_CXX "c++-header") + if (_flags) + # append to list + list (APPEND _flags "-x" "${_xLanguage_${_language}}" "-c" "${_prefixFile}" -o "${_pchFile}") + else() + # return as a flag string + set (_flags "-x ${_xLanguage_${_language}} -c \"${_prefixFile}\" -o \"${_pchFile}\"") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + file (TO_NATIVE_PATH "${_hostFile}" _hostFileNative) + # Windows Intel options used + # /nologo do not display compiler version information + # /Yc create a precompiled header (PCH) file + # /Fp specify a path or file name for precompiled header files + # /FI tells the preprocessor to include a specified file name as the header file + # /TC process all source or unrecognized file types as C source files + # /TP process all source or unrecognized file types as C++ source files + # /Zs syntax check only + # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + set (_sourceFileTypeC "/TC") + set (_sourceFileTypeCXX "/TP") + if (_flags) + # append to list + list (APPEND _flags /nologo "${_sourceFileType${_language}}" + "/Yc" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}" /Zs "${_hostFileNative}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "/Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "/Yc /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} /Wpch-messages") + endif() + endif() + else() + # Linux / Mac OS X Intel options used + # -pch-dir location for precompiled header files + # -pch-create name of the precompiled header (PCH) to create + # -Kc++ process all source or unrecognized file types as C++ source files + # -fsyntax-only check only for correct syntax + # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + get_filename_component(_pchDir "${_pchFile}" DIRECTORY) + get_filename_component(_pchName "${_pchFile}" NAME) + set (_xLanguage_C "c-header") + set (_xLanguage_CXX "c++-header") + if (_flags) + # append to list + if ("${_language}" STREQUAL "CXX") + list (APPEND _flags -Kc++) + endif() + list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-create" "${_pchName}" "-fsyntax-only" "${_hostFile}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "-Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-create \"${_pchName}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} -Wpch-messages") + endif() + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_add_prefix_pch_inclusion_flags _language _compilerID _compilerVersion _prefixFile _pchFile _flagsVar) + set (_flags ${${_flagsVar}}) + if (_compilerID MATCHES "MSVC") + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + # cl.exe options used + # /Yu uses a precompiled header file during build + # /Fp specifies precompiled header binary file name + # /FI forces inclusion of file + # /Zm precompiled header memory allocation scaling factor + if (_pchFile) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + if (_flags) + # append to list + list (APPEND _flags "/Yu${_prefixFileNative}" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + list (APPEND _flags "/Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + else() + # return as a flag string + set (_flags "/Yu\"${_prefixFileNative}\" /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (_flags "${_flags} /Zm${COTIRE_PCH_MEMORY_SCALING_FACTOR}") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "/FI${_prefixFileNative}") + else() + # return as a flag string + set (_flags "/FI\"${_prefixFileNative}\"") + endif() + endif() + elseif (_compilerID MATCHES "GNU") + # GCC options used + # -include process include file as the first line of the primary source file + # -Winvalid-pch warns if precompiled header is found but cannot be used + # note: ccache requires the -include flag to be used in order to process precompiled header correctly + if (_flags) + # append to list + list (APPEND _flags "-Winvalid-pch" "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-Winvalid-pch -include \"${_prefixFile}\"") + endif() + elseif (_compilerID MATCHES "Clang") + # Clang options used + # -include process include file as the first line of the primary source file + # -include-pch include precompiled header file + # -Qunused-arguments don't emit warning for unused driver arguments + # note: ccache requires the -include flag to be used in order to process precompiled header correctly + if (_flags) + # append to list + list (APPEND _flags "-Qunused-arguments" "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-Qunused-arguments -include \"${_prefixFile}\"") + endif() + elseif (_compilerID MATCHES "Intel") + if (WIN32) + file (TO_NATIVE_PATH "${_prefixFile}" _prefixFileNative) + # Windows Intel options used + # /Yu use a precompiled header (PCH) file + # /Fp specify a path or file name for precompiled header files + # /FI tells the preprocessor to include a specified file name as the header file + # /Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + if (_pchFile) + file (TO_NATIVE_PATH "${_pchFile}" _pchFileNative) + if (_flags) + # append to list + list (APPEND _flags "/Yu" "/Fp${_pchFileNative}" "/FI${_prefixFileNative}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "/Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "/Yu /Fp\"${_pchFileNative}\" /FI\"${_prefixFileNative}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} /Wpch-messages") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "/FI${_prefixFileNative}") + else() + # return as a flag string + set (_flags "/FI\"${_prefixFileNative}\"") + endif() + endif() + else() + # Linux / Mac OS X Intel options used + # -pch-dir location for precompiled header files + # -pch-use name of the precompiled header (PCH) to use + # -include process include file as the first line of the primary source file + # -Wpch-messages enable diagnostics related to pre-compiled headers (requires Intel XE 2013 Update 2) + if (_pchFile) + get_filename_component(_pchDir "${_pchFile}" DIRECTORY) + get_filename_component(_pchName "${_pchFile}" NAME) + if (_flags) + # append to list + list (APPEND _flags "-include" "${_prefixFile}" "-pch-dir" "${_pchDir}" "-pch-use" "${_pchName}") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + list (APPEND _flags "-Wpch-messages") + endif() + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\" -pch-dir \"${_pchDir}\" -pch-use \"${_pchName}\"") + if (NOT "${_compilerVersion}" VERSION_LESS "13.1.0") + set (_flags "${_flags} -Wpch-messages") + endif() + endif() + else() + # no precompiled header, force inclusion of prefix header + if (_flags) + # append to list + list (APPEND _flags "-include" "${_prefixFile}") + else() + # return as a flag string + set (_flags "-include \"${_prefixFile}\"") + endif() + endif() + endif() + else() + message (FATAL_ERROR "cotire: unsupported ${_language} compiler ${_compilerID} version ${_compilerVersion}.") + endif() + set (${_flagsVar} ${_flags} PARENT_SCOPE) +endfunction() + +function (cotire_precompile_prefix_header _prefixFile _pchFile _hostFile) + set(_options "") + set(_oneValueArgs COMPILER_EXECUTABLE COMPILER_ID COMPILER_VERSION LANGUAGE) + set(_multiValueArgs COMPILE_DEFINITIONS COMPILE_FLAGS INCLUDE_DIRECTORIES SYSTEM_INCLUDE_DIRECTORIES SYS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_LANGUAGE) + set (_option_LANGUAGE "CXX") + endif() + if (NOT _option_COMPILER_ID) + set (_option_COMPILER_ID "${CMAKE_${_option_LANGUAGE}_ID}") + endif() + if (NOT _option_COMPILER_VERSION) + set (_option_COMPILER_VERSION "${CMAKE_${_option_LANGUAGE}_COMPILER_VERSION}") + endif() + cotire_init_compile_cmd(_cmd "${_option_LANGUAGE}" "${_option_COMPILER_EXECUTABLE}" "${_option_COMPILER_ARG1}") + cotire_add_definitions_to_cmd(_cmd "${_option_LANGUAGE}" ${_option_COMPILE_DEFINITIONS}) + cotire_add_compile_flags_to_cmd(_cmd ${_option_COMPILE_FLAGS}) + cotire_add_includes_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_frameworks_to_cmd(_cmd "${_option_LANGUAGE}" _option_INCLUDE_DIRECTORIES _option_SYSTEM_INCLUDE_DIRECTORIES) + cotire_add_pch_compilation_flags( + "${_option_LANGUAGE}" "${_option_COMPILER_ID}" "${_option_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" "${_hostFile}" _cmd) + if (COTIRE_VERBOSE) + message (STATUS "execute_process: ${_cmd}") + endif() + if (_option_COMPILER_ID MATCHES "MSVC") + # cl.exe messes with the output streams unless the environment variable VS_UNICODE_OUTPUT is cleared + unset (ENV{VS_UNICODE_OUTPUT}) + endif() + execute_process( + COMMAND ${_cmd} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE _result) + if (_result) + message (FATAL_ERROR "cotire: error ${_result} precompiling ${_prefixFile}.") + endif() +endfunction() + +function (cotire_check_precompiled_header_support _language _target _msgVar) + set (_unsupportedCompiler + "Precompiled headers not supported for ${_language} compiler ${CMAKE_${_language}_COMPILER_ID}") + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") + # supported since Visual Studio C++ 6.0 + # and CMake does not support an earlier version + set (${_msgVar} "" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") + # GCC PCH support requires version >= 3.4 + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "3.4.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + else() + set (${_msgVar} "" PARENT_SCOPE) + endif() + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") + # all Clang versions have PCH support + set (${_msgVar} "" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") + # Intel PCH support requires version >= 8.0.0 + if ("${CMAKE_${_language}_COMPILER_VERSION}" VERSION_LESS "8.0.0") + set (${_msgVar} "${_unsupportedCompiler} version ${CMAKE_${_language}_COMPILER_VERSION}." PARENT_SCOPE) + else() + set (${_msgVar} "" PARENT_SCOPE) + endif() + else() + set (${_msgVar} "${_unsupportedCompiler}." PARENT_SCOPE) + endif() + if (CMAKE_${_language}_COMPILER MATCHES "ccache") + if (NOT "$ENV{CCACHE_SLOPPINESS}" MATCHES "time_macros|pch_defines") + set (${_msgVar} + "ccache requires the environment variable CCACHE_SLOPPINESS to be set to \"pch_defines,time_macros\"." + PARENT_SCOPE) + endif() + endif() + if (APPLE) + # PCH compilation not supported by GCC / Clang for multi-architecture builds (e.g., i386, x86_64) + cotire_get_configuration_types(_configs) + foreach (_config ${_configs}) + set (_targetFlags "") + cotire_get_target_compile_flags("${_config}" "${_language}" "${_target}" _targetFlags) + cotire_filter_compile_flags("${_language}" "arch" _architectures _ignore ${_targetFlags}) + list (LENGTH _architectures _numberOfArchitectures) + if (_numberOfArchitectures GREATER 1) + string (REPLACE ";" ", " _architectureStr "${_architectures}") + set (${_msgVar} + "Precompiled headers not supported on Darwin for multi-architecture builds (${_architectureStr})." + PARENT_SCOPE) + break() + endif() + endforeach() + endif() +endfunction() + +macro (cotire_get_intermediate_dir _cotireDir) + # ${CMAKE_CFG_INTDIR} may reference a build-time variable when using a generator which supports configuration types + get_filename_component(${_cotireDir} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${COTIRE_INTDIR}" ABSOLUTE) +endmacro() + +macro (cotire_setup_file_extension_variables) + set (_unityFileExt_C ".c") + set (_unityFileExt_CXX ".cxx") + set (_prefixFileExt_C ".h") + set (_prefixFileExt_CXX ".hxx") + set (_prefixSourceFileExt_C ".c") + set (_prefixSourceFileExt_CXX ".cxx") +endmacro() + +function (cotire_make_single_unity_source_file_path _language _target _unityFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_unityFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + set (_unityFileName "${_unityFileBaseName}${_unityFileExt_${_language}}") + cotire_get_intermediate_dir(_baseDir) + set (_unityFile "${_baseDir}/${_unityFileName}") + set (${_unityFileVar} "${_unityFile}" PARENT_SCOPE) +endfunction() + +function (cotire_make_unity_source_file_paths _language _target _maxIncludes _unityFilesVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_unityFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + cotire_get_intermediate_dir(_baseDir) + set (_startIndex 0) + set (_index 0) + set (_unityFiles "") + set (_sourceFiles ${ARGN}) + foreach (_sourceFile ${_sourceFiles}) + get_source_file_property(_startNew "${_sourceFile}" COTIRE_START_NEW_UNITY_SOURCE) + math (EXPR _unityFileCount "${_index} - ${_startIndex}") + if (_startNew OR (_maxIncludes GREATER 0 AND NOT _unityFileCount LESS _maxIncludes)) + if (_index GREATER 0) + # start new unity file segment + math (EXPR _endIndex "${_index} - 1") + set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") + list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") + endif() + set (_startIndex ${_index}) + endif() + math (EXPR _index "${_index} + 1") + endforeach() + list (LENGTH _sourceFiles _numberOfSources) + if (_startIndex EQUAL 0) + # there is only a single unity file + cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFiles) + elseif (_startIndex LESS _numberOfSources) + # end with final unity file segment + math (EXPR _endIndex "${_index} - 1") + set (_unityFileName "${_unityFileBaseName}_${_startIndex}_${_endIndex}${_unityFileExt_${_language}}") + list (APPEND _unityFiles "${_baseDir}/${_unityFileName}") + endif() + set (${_unityFilesVar} ${_unityFiles} PARENT_SCOPE) + if (COTIRE_DEBUG AND _unityFiles) + message (STATUS "unity files: ${_unityFiles}") + endif() +endfunction() + +function (cotire_unity_to_prefix_file_path _language _target _unityFile _prefixFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _unityFileExt_${_language}) + set (${_prefixFileVar} "" PARENT_SCOPE) + return() + endif() + set (_unityFileBaseName "${_target}_${_language}${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}") + set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + string (REPLACE "${_unityFileBaseName}" "${_prefixFileBaseName}" _prefixFile "${_unityFile}") + string (REGEX REPLACE "${_unityFileExt_${_language}}$" "${_prefixFileExt_${_language}}" _prefixFile "${_prefixFile}") + set (${_prefixFileVar} "${_prefixFile}" PARENT_SCOPE) +endfunction() + +function (cotire_prefix_header_to_source_file_path _language _prefixHeaderFile _prefixSourceFileVar) + cotire_setup_file_extension_variables() + if (NOT DEFINED _prefixSourceFileExt_${_language}) + set (${_prefixSourceFileVar} "" PARENT_SCOPE) + return() + endif() + string (REGEX REPLACE "${_prefixFileExt_${_language}}$" "${_prefixSourceFileExt_${_language}}" _prefixSourceFile "${_prefixHeaderFile}") + set (${_prefixSourceFileVar} "${_prefixSourceFile}" PARENT_SCOPE) +endfunction() + +function (cotire_make_prefix_file_name _language _target _prefixFileBaseNameVar _prefixFileNameVar) + cotire_setup_file_extension_variables() + if (NOT _language) + set (_prefixFileBaseName "${_target}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_C}") + elseif (DEFINED _prefixFileExt_${_language}) + set (_prefixFileBaseName "${_target}_${_language}${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}") + set (_prefixFileName "${_prefixFileBaseName}${_prefixFileExt_${_language}}") + else() + set (_prefixFileBaseName "") + set (_prefixFileName "") + endif() + set (${_prefixFileBaseNameVar} "${_prefixFileBaseName}" PARENT_SCOPE) + set (${_prefixFileNameVar} "${_prefixFileName}" PARENT_SCOPE) +endfunction() + +function (cotire_make_prefix_file_path _language _target _prefixFileVar) + cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) + set (${_prefixFileVar} "" PARENT_SCOPE) + if (_prefixFileName) + if (NOT _language) + set (_language "C") + endif() + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang|Intel|MSVC") + cotire_get_intermediate_dir(_baseDir) + set (${_prefixFileVar} "${_baseDir}/${_prefixFileName}" PARENT_SCOPE) + endif() + endif() +endfunction() + +function (cotire_make_pch_file_path _language _target _pchFileVar) + cotire_make_prefix_file_name("${_language}" "${_target}" _prefixFileBaseName _prefixFileName) + set (${_pchFileVar} "" PARENT_SCOPE) + if (_prefixFileBaseName AND _prefixFileName) + cotire_check_precompiled_header_support("${_language}" "${_target}" _msg) + if (NOT _msg) + if (XCODE) + # For Xcode, we completely hand off the compilation of the prefix header to the IDE + return() + endif() + cotire_get_intermediate_dir(_baseDir) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC") + # MSVC uses the extension .pch added to the prefix header base name + set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Clang") + # Clang looks for a precompiled header corresponding to the prefix header with the extension .pch appended + set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.pch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "GNU") + # GCC looks for a precompiled header corresponding to the prefix header with the extension .gch appended + set (${_pchFileVar} "${_baseDir}/${_prefixFileName}.gch" PARENT_SCOPE) + elseif (CMAKE_${_language}_COMPILER_ID MATCHES "Intel") + # Intel uses the extension .pchi added to the prefix header base name + set (${_pchFileVar} "${_baseDir}/${_prefixFileBaseName}.pchi" PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + +function (cotire_select_unity_source_files _unityFile _sourcesVar) + set (_sourceFiles ${ARGN}) + if (_sourceFiles AND "${_unityFile}" MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}_([0-9]+)_([0-9]+)") + set (_startIndex ${CMAKE_MATCH_1}) + set (_endIndex ${CMAKE_MATCH_2}) + list (LENGTH _sourceFiles _numberOfSources) + if (NOT _startIndex LESS _numberOfSources) + math (EXPR _startIndex "${_numberOfSources} - 1") + endif() + if (NOT _endIndex LESS _numberOfSources) + math (EXPR _endIndex "${_numberOfSources} - 1") + endif() + set (_files "") + foreach (_index RANGE ${_startIndex} ${_endIndex}) + list (GET _sourceFiles ${_index} _file) + list (APPEND _files "${_file}") + endforeach() + else() + set (_files ${_sourceFiles}) + endif() + set (${_sourcesVar} ${_files} PARENT_SCOPE) +endfunction() + +function (cotire_get_unity_source_dependencies _language _target _dependencySourcesVar) + set (_dependencySources "") + # depend on target's generated source files + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) + if (_generatedSources) + # but omit all generated source files that have the COTIRE_EXCLUDED property set to true + cotire_get_objects_with_property_on(_excludedGeneratedSources COTIRE_EXCLUDED SOURCE ${_generatedSources}) + if (_excludedGeneratedSources) + list (REMOVE_ITEM _generatedSources ${_excludedGeneratedSources}) + endif() + # and omit all generated source files that have the COTIRE_DEPENDENCY property set to false explicitly + cotire_get_objects_with_property_off(_excludedNonDependencySources COTIRE_DEPENDENCY SOURCE ${_generatedSources}) + if (_excludedNonDependencySources) + list (REMOVE_ITEM _generatedSources ${_excludedNonDependencySources}) + endif() + if (_generatedSources) + list (APPEND _dependencySources ${_generatedSources}) + endif() + endif() + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_language} ${_target} unity source dependencies: ${_dependencySources}") + endif() + set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) +endfunction() + +function (cotire_get_prefix_header_dependencies _language _target _dependencySourcesVar) + set (_dependencySources "") + # depend on target source files marked with custom COTIRE_DEPENDENCY property + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_dependencySources COTIRE_DEPENDENCY SOURCE ${_targetSourceFiles}) + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_language} ${_target} prefix header dependencies: ${_dependencySources}") + endif() + set (${_dependencySourcesVar} ${_dependencySources} PARENT_SCOPE) +endfunction() + +function (cotire_generate_target_script _language _configurations _target _targetScriptVar _targetConfigScriptVar) + set (_targetSources ${ARGN}) + cotire_get_prefix_header_dependencies(${_language} ${_target} COTIRE_TARGET_PREFIX_DEPENDS ${_targetSources}) + cotire_get_unity_source_dependencies(${_language} ${_target} COTIRE_TARGET_UNITY_DEPENDS ${_targetSources}) + # set up variables to be configured + set (COTIRE_TARGET_LANGUAGE "${_language}") + get_target_property(COTIRE_TARGET_IGNORE_PATH ${_target} COTIRE_PREFIX_HEADER_IGNORE_PATH) + cotire_add_sys_root_paths(COTIRE_TARGET_IGNORE_PATH) + get_target_property(COTIRE_TARGET_INCLUDE_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PATH) + cotire_add_sys_root_paths(COTIRE_TARGET_INCLUDE_PATH) + get_target_property(COTIRE_TARGET_PRE_UNDEFS ${_target} COTIRE_UNITY_SOURCE_PRE_UNDEFS) + get_target_property(COTIRE_TARGET_POST_UNDEFS ${_target} COTIRE_UNITY_SOURCE_POST_UNDEFS) + get_target_property(COTIRE_TARGET_MAXIMUM_NUMBER_OF_INCLUDES ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) + get_target_property(COTIRE_TARGET_INCLUDE_PRIORITY_PATH ${_target} COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_PRE_UNDEFS COTIRE_TARGET_SOURCES_PRE_UNDEFS ${_targetSources}) + cotire_get_source_files_undefs(COTIRE_UNITY_SOURCE_POST_UNDEFS COTIRE_TARGET_SOURCES_POST_UNDEFS ${_targetSources}) + set (COTIRE_TARGET_CONFIGURATION_TYPES "${_configurations}") + foreach (_config ${_configurations}) + string (TOUPPER "${_config}" _upperConfig) + cotire_get_target_include_directories( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig} COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}) + cotire_get_target_compile_definitions( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}) + cotire_get_target_compiler_flags( + "${_config}" "${_language}" "${_target}" COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}) + cotire_get_source_files_compile_definitions( + "${_config}" "${_language}" COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig} ${_targetSources}) + endforeach() + # set up COTIRE_TARGET_SOURCES + set (COTIRE_TARGET_SOURCES "") + foreach (_sourceFile ${_targetSources}) + get_source_file_property(_generated "${_sourceFile}" GENERATED) + if (_generated) + # use absolute paths for generated files only, retrieving the LOCATION property is an expensive operation + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND COTIRE_TARGET_SOURCES "${_sourceLocation}") + else() + list (APPEND COTIRE_TARGET_SOURCES "${_sourceFile}") + endif() + endforeach() + # copy variable definitions to cotire target script + get_cmake_property(_vars VARIABLES) + string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+" _matchVars "${_vars}") + # omit COTIRE_*_INIT variables + string (REGEX MATCHALL "COTIRE_[A-Za-z0-9_]+_INIT" _initVars "${_matchVars}") + if (_initVars) + list (REMOVE_ITEM _matchVars ${_initVars}) + endif() + # omit COTIRE_VERBOSE which is passed as a CMake define on command line + list (REMOVE_ITEM _matchVars COTIRE_VERBOSE) + set (_contents "") + set (_contentsHasGeneratorExpressions FALSE) + foreach (_var IN LISTS _matchVars ITEMS + XCODE MSVC CMAKE_GENERATOR CMAKE_BUILD_TYPE CMAKE_CONFIGURATION_TYPES + CMAKE_${_language}_COMPILER_ID CMAKE_${_language}_COMPILER_VERSION + CMAKE_${_language}_COMPILER CMAKE_${_language}_COMPILER_ARG1 + CMAKE_INCLUDE_FLAG_${_language} CMAKE_INCLUDE_FLAG_${_language}_SEP + CMAKE_INCLUDE_SYSTEM_FLAG_${_language} + CMAKE_${_language}_FRAMEWORK_SEARCH_FLAG + CMAKE_${_language}_SYSTEM_FRAMEWORK_SEARCH_FLAG + CMAKE_${_language}_SOURCE_FILE_EXTENSIONS) + if (DEFINED ${_var}) + string (REPLACE "\"" "\\\"" _value "${${_var}}") + set (_contents "${_contents}set (${_var} \"${_value}\")\n") + if (NOT _contentsHasGeneratorExpressions) + if ("${_value}" MATCHES "\\$<.*>") + set (_contentsHasGeneratorExpressions TRUE) + endif() + endif() + endif() + endforeach() + # generate target script file + get_filename_component(_moduleName "${COTIRE_CMAKE_MODULE_FILE}" NAME) + set (_targetCotireScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_moduleName}") + cotire_write_file("CMAKE" "${_targetCotireScript}" "${_contents}" FALSE) + if (_contentsHasGeneratorExpressions) + # use file(GENERATE ...) to expand generator expressions in the target script at CMake generate-time + set (_configNameOrNoneGeneratorExpression "$<$:None>$<$>:$>") + set (_targetCotireConfigScript "${CMAKE_CURRENT_BINARY_DIR}/${_target}_${_language}_${_configNameOrNoneGeneratorExpression}_${_moduleName}") + file (GENERATE OUTPUT "${_targetCotireConfigScript}" INPUT "${_targetCotireScript}") + else() + set (_targetCotireConfigScript "${_targetCotireScript}") + endif() + set (${_targetScriptVar} "${_targetCotireScript}" PARENT_SCOPE) + set (${_targetConfigScriptVar} "${_targetCotireConfigScript}" PARENT_SCOPE) +endfunction() + +function (cotire_setup_pch_file_compilation _language _target _targetScript _prefixFile _pchFile _hostFile) + set (_sourceFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # for Visual Studio and Intel, we attach the precompiled header compilation to the host file + # the remaining files include the precompiled header, see cotire_setup_pch_file_inclusion + if (_sourceFiles) + set (_flags "") + cotire_add_pch_compilation_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" "${_hostFile}" _flags) + set_property (SOURCE ${_hostFile} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_OUTPUTS "${_pchFile}") + # make object file generated from host file depend on prefix header + set_property (SOURCE ${_hostFile} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") + # mark host file as cotired to prevent it from being used in another cotired target + set_property (SOURCE ${_hostFile} PROPERTY COTIRE_TARGET "${_target}") + endif() + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generator, we add a custom command to precompile the prefix header + if (_targetScript) + cotire_set_cmd_to_prologue(_cmds) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "precompile" "${_targetScript}" "${_prefixFile}" "${_pchFile}" "${_hostFile}") + file (RELATIVE_PATH _pchFileRelPath "${CMAKE_BINARY_DIR}" "${_pchFile}") + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_pchFile} ${_cmds} DEPENDS ${_prefixFile} IMPLICIT_DEPENDS ${_language} ${_prefixFile}") + endif() + set_property (SOURCE "${_pchFile}" PROPERTY GENERATED TRUE) + add_custom_command( + OUTPUT "${_pchFile}" + COMMAND ${_cmds} + DEPENDS "${_prefixFile}" + IMPLICIT_DEPENDS ${_language} "${_prefixFile}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Building ${_language} precompiled header ${_pchFileRelPath}" + VERBATIM) + endif() + endif() +endfunction() + +function (cotire_setup_pch_file_inclusion _language _target _wholeTarget _prefixFile _pchFile _hostFile) + if (CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # for Visual Studio and Intel, we include the precompiled header in all but the host file + # the host file does the precompiled header compilation, see cotire_setup_pch_file_compilation + set (_sourceFiles ${ARGN}) + list (LENGTH _sourceFiles _numberOfSourceFiles) + if (_numberOfSourceFiles GREATER 0) + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + set (_flags "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # make object files generated from source files depend on precompiled header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") + endif() + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + set (_sourceFiles ${_hostFile} ${ARGN}) + if (NOT _wholeTarget) + # for makefile based generator, we force the inclusion of the prefix header for a subset + # of the source files, if this is a multi-language target or has excluded files + set (_flags "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + endif() + # make object files generated from source files depend on precompiled header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_pchFile}") + endif() +endfunction() + +function (cotire_setup_prefix_file_inclusion _language _target _prefixFile) + set (_sourceFiles ${ARGN}) + # force the inclusion of the prefix header for the given source files + set (_flags "") + set (_pchFile "") + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _flags) + set_property (SOURCE ${_sourceFiles} APPEND_STRING PROPERTY COMPILE_FLAGS " ${_flags} ") + # mark sources as cotired to prevent them from being used in another cotired target + set_source_files_properties(${_sourceFiles} PROPERTIES COTIRE_TARGET "${_target}") + # make object files generated from source files depend on prefix header + set_property (SOURCE ${_sourceFiles} APPEND PROPERTY OBJECT_DEPENDS "${_prefixFile}") +endfunction() + +function (cotire_get_first_set_property_value _propertyValueVar _type _object) + set (_properties ${ARGN}) + foreach (_property ${_properties}) + get_property(_propertyValue ${_type} "${_object}" PROPERTY ${_property}) + if (_propertyValue) + set (${_propertyValueVar} ${_propertyValue} PARENT_SCOPE) + return() + endif() + endforeach() + set (${_propertyValueVar} "" PARENT_SCOPE) +endfunction() + +function (cotire_setup_combine_command _language _targetScript _joinedFile _cmdsVar) + set (_files ${ARGN}) + set (_filesPaths "") + foreach (_file ${_files}) + get_filename_component(_filePath "${_file}" ABSOLUTE) + list (APPEND _filesPaths "${_filePath}") + endforeach() + cotire_set_cmd_to_prologue(_prefixCmd) + list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "combine") + if (_targetScript) + list (APPEND _prefixCmd "${_targetScript}") + endif() + list (APPEND _prefixCmd "${_joinedFile}" ${_filesPaths}) + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_joinedFile} COMMAND ${_prefixCmd} DEPENDS ${_files}") + endif() + set_property (SOURCE "${_joinedFile}" PROPERTY GENERATED TRUE) + file (RELATIVE_PATH _joinedFileRelPath "${CMAKE_BINARY_DIR}" "${_joinedFile}") + get_filename_component(_joinedFileBaseName "${_joinedFile}" NAME_WE) + get_filename_component(_joinedFileExt "${_joinedFile}" EXT) + if (_language AND _joinedFileBaseName MATCHES "${COTIRE_UNITY_SOURCE_FILENAME_SUFFIX}$") + set (_comment "Generating ${_language} unity source ${_joinedFileRelPath}") + elseif (_language AND _joinedFileBaseName MATCHES "${COTIRE_PREFIX_HEADER_FILENAME_SUFFIX}$") + if (_joinedFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_joinedFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_joinedFileRelPath}") + endif() + else() + set (_comment "Generating ${_joinedFileRelPath}") + endif() + add_custom_command( + OUTPUT "${_joinedFile}" + COMMAND ${_prefixCmd} + DEPENDS ${_files} + COMMENT "${_comment}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_target_pch_usage _languages _target _wholeTarget) + if (XCODE) + # for Xcode, we attach a pre-build action to generate the unity sources and prefix headers + set (_prefixFiles "") + foreach (_language ${_languages}) + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_prefixFile) + list (APPEND _prefixFiles "${_prefixFile}") + endif() + endforeach() + set (_cmds ${ARGN}) + list (LENGTH _prefixFiles _numberOfPrefixFiles) + if (_numberOfPrefixFiles GREATER 1) + # we also generate a generic, single prefix header which includes all language specific prefix headers + set (_language "") + set (_targetScript "") + cotire_make_prefix_file_path("${_language}" ${_target} _prefixHeader) + cotire_setup_combine_command("${_language}" "${_targetScript}" "${_prefixHeader}" _cmds ${_prefixFiles}) + else() + set (_prefixHeader "${_prefixFiles}") + endif() + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: TARGET ${_target} PRE_BUILD ${_cmds}") + endif() + # because CMake PRE_BUILD command does not support dependencies, + # we check dependencies explicity in cotire script mode when the pre-build action is run + add_custom_command( + TARGET "${_target}" + PRE_BUILD ${_cmds} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Updating target ${_target} prefix headers" + VERBATIM) + # make Xcode precompile the generated prefix header with ProcessPCH and ProcessPCH++ + set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES") + set_target_properties(${_target} PROPERTIES XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${_prefixHeader}") + elseif ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generator, we force inclusion of the prefix header for all target source files + # if this is a single-language target without any excluded files + if (_wholeTarget) + set (_language "${_languages}") + # for Visual Studio and Intel, precompiled header inclusion is always done on the source file level + # see cotire_setup_pch_file_inclusion + if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_prefixFile) + get_property(_pchFile TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER) + set (_options COMPILE_OPTIONS) + cotire_add_prefix_pch_inclusion_flags( + "${_language}" "${CMAKE_${_language}_COMPILER_ID}" "${CMAKE_${_language}_COMPILER_VERSION}" + "${_prefixFile}" "${_pchFile}" _options) + set_property(TARGET ${_target} APPEND PROPERTY ${_options}) + endif() + endif() + endif() + endif() +endfunction() + +function (cotire_setup_unity_generation_commands _language _target _targetScript _targetConfigScript _unityFiles _cmdsVar) + set (_dependencySources "") + cotire_get_unity_source_dependencies(${_language} ${_target} _dependencySources ${ARGN}) + foreach (_unityFile ${_unityFiles}) + set_property (SOURCE "${_unityFile}" PROPERTY GENERATED TRUE) + # set up compiled unity source dependencies via OBJECT_DEPENDS + # this ensures that missing source files are generated before the unity file is compiled + if (COTIRE_DEBUG AND _dependencySources) + message (STATUS "${_unityFile} OBJECT_DEPENDS ${_dependencySources}") + endif() + if (_dependencySources) + # the OBJECT_DEPENDS property requires a list of full paths + set (_objectDependsPaths "") + foreach (_sourceFile ${_dependencySources}) + get_source_file_property(_sourceLocation "${_sourceFile}" LOCATION) + list (APPEND _objectDependsPaths "${_sourceLocation}") + endforeach() + set_property (SOURCE "${_unityFile}" PROPERTY OBJECT_DEPENDS ${_objectDependsPaths}) + endif() + if (WIN32 AND CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # unity file compilation results in potentially huge object file, thus use /bigobj by default unter MSVC and Windows Intel + set_property (SOURCE "${_unityFile}" APPEND_STRING PROPERTY COMPILE_FLAGS "/bigobj") + endif() + cotire_set_cmd_to_prologue(_unityCmd) + list (APPEND _unityCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "unity" "${_targetConfigScript}" "${_unityFile}") + if (CMAKE_VERSION VERSION_LESS "3.1.0") + set (_unityCmdDepends "${_targetScript}") + else() + # CMake 3.1.0 supports generator expressions in arguments to DEPENDS + set (_unityCmdDepends "${_targetConfigScript}") + endif() + file (RELATIVE_PATH _unityFileRelPath "${CMAKE_BINARY_DIR}" "${_unityFile}") + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_unityFile} COMMAND ${_unityCmd} DEPENDS ${_unityCmdDepends}") + endif() + add_custom_command( + OUTPUT "${_unityFile}" + COMMAND ${_unityCmd} + DEPENDS ${_unityCmdDepends} + COMMENT "Generating ${_language} unity source ${_unityFileRelPath}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_unityCmd}) + endforeach() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) + set (_sourceFiles ${ARGN}) + set (_dependencySources "") + cotire_get_prefix_header_dependencies(${_language} ${_target} _dependencySources ${_sourceFiles}) + cotire_set_cmd_to_prologue(_prefixCmd) + list (APPEND _prefixCmd -P "${COTIRE_CMAKE_MODULE_FILE}" "prefix" "${_targetScript}" "${_prefixFile}" ${_unityFiles}) + set_property (SOURCE "${_prefixFile}" PROPERTY GENERATED TRUE) + if (COTIRE_DEBUG) + message (STATUS "add_custom_command: OUTPUT ${_prefixFile} COMMAND ${_prefixCmd} DEPENDS ${_unityFile} ${_dependencySources}") + endif() + file (RELATIVE_PATH _prefixFileRelPath "${CMAKE_BINARY_DIR}" "${_prefixFile}") + get_filename_component(_prefixFileExt "${_prefixFile}" EXT) + if (_prefixFileExt MATCHES "^\\.c") + set (_comment "Generating ${_language} prefix source ${_prefixFileRelPath}") + else() + set (_comment "Generating ${_language} prefix header ${_prefixFileRelPath}") + endif() + # prevent pre-processing errors upon generating the prefix header when a target's generated include file does not yet exist + # we do not add a file-level dependency for the target's generated files though, because we only want to depend on their existence + # thus we make the prefix header generation depend on a custom helper target which triggers the generation of the files + set (_preTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}_pre") + if (TARGET ${_preTargetName}) + # custom helper target has already been generated while processing a different language + list (APPEND _dependencySources ${_preTargetName}) + else() + get_target_property(_targetSourceFiles ${_target} SOURCES) + cotire_get_objects_with_property_on(_generatedSources GENERATED SOURCE ${_targetSourceFiles}) + if (_generatedSources) + add_custom_target("${_preTargetName}" DEPENDS ${_generatedSources}) + cotire_init_target("${_preTargetName}") + list (APPEND _dependencySources ${_preTargetName}) + endif() + endif() + add_custom_command( + OUTPUT "${_prefixFile}" "${_prefixFile}.log" + COMMAND ${_prefixCmd} + DEPENDS ${_unityFiles} ${_dependencySources} + COMMENT "${_comment}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + list (APPEND ${_cmdsVar} COMMAND ${_prefixCmd}) + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_from_unity_command _language _target _targetScript _prefixFile _unityFiles _cmdsVar) + set (_sourceFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() + cotire_setup_prefix_generation_command( + ${_language} ${_target} "${_targetScript}" + "${_prefixSourceFile}" "${_unityFiles}" ${_cmdsVar} ${_sourceFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) + endif() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_setup_prefix_generation_from_provided_command _language _target _targetScript _prefixFile _cmdsVar) + set (_prefixHeaderFiles ${ARGN}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # GNU and Clang require indirect compilation of the prefix header to make them honor the system_header pragma + cotire_prefix_header_to_source_file_path(${_language} "${_prefixFile}" _prefixSourceFile) + else() + set (_prefixSourceFile "${_prefixFile}") + endif() + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixSourceFile}" _cmds ${_prefixHeaderFiles}) + if (CMAKE_${_language}_COMPILER_ID MATCHES "GNU|Clang") + # set up generation of a prefix source file which includes the prefix header + cotire_setup_combine_command(${_language} "${_targetScript}" "${_prefixFile}" _cmds ${_prefixSourceFile}) + endif() + set (${_cmdsVar} ${${_cmdsVar}} PARENT_SCOPE) +endfunction() + +function (cotire_init_cotire_target_properties _target) + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER TRUE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD TRUE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_CLEAN FALSE) + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_SOURCE_DIR}") + cotire_check_is_path_relative_to("${CMAKE_BINARY_DIR}" _isRelative "${CMAKE_SOURCE_DIR}") + if (NOT _isRelative) + set_property(TARGET ${_target} APPEND PROPERTY COTIRE_PREFIX_HEADER_IGNORE_PATH "${CMAKE_BINARY_DIR}") + endif() + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PATH "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_PRE_UNDEFS "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_POST_UNDEFS "") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT SET) + if (NOT _isSet) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_LINK_LIBRARIES_INIT "COPY_UNITY") + endif() + get_property(_isSet TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES SET) + if (NOT _isSet) + if (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}") + else() + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "") + endif() + endif() +endfunction() + +function (cotire_make_target_message _target _languages _disableMsg _targetMsgVar) + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + string (REPLACE ";" " " _languagesStr "${_languages}") + math (EXPR _numberOfExcludedFiles "${ARGC} - 4") + if (_numberOfExcludedFiles EQUAL 0) + set (_excludedStr "") + elseif (COTIRE_VERBOSE OR _numberOfExcludedFiles LESS 4) + string (REPLACE ";" ", " _excludedStr "excluding ${ARGN}") + else() + set (_excludedStr "excluding ${_numberOfExcludedFiles} files") + endif() + set (_targetMsg "") + if (NOT _languages) + set (_targetMsg "Target ${_target} cannot be cotired.") + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetUsePCH AND NOT _targetAddSCU) + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build and precompiled header.") + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetUsePCH) + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired without precompiled header.") + endif() + if (_disableMsg) + set (_targetMsg "${_targetMsg} ${_disableMsg}") + endif() + elseif (NOT _targetAddSCU) + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired without unity build.") + endif() + else() + if (_excludedStr) + set (_targetMsg "${_languagesStr} target ${_target} cotired ${_excludedStr}.") + else() + set (_targetMsg "${_languagesStr} target ${_target} cotired.") + endif() + endif() + set (${_targetMsgVar} "${_targetMsg}" PARENT_SCOPE) +endfunction() + +function (cotire_choose_target_languages _target _targetLanguagesVar _wholeTargetVar) + set (_languages ${ARGN}) + set (_allSourceFiles "") + set (_allExcludedSourceFiles "") + set (_allCotiredSourceFiles "") + set (_targetLanguages "") + set (_pchEligibleTargetLanguages "") + get_target_property(_targetType ${_target} TYPE) + get_target_property(_targetSourceFiles ${_target} SOURCES) + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + set (_disableMsg "") + foreach (_language ${_languages}) + get_target_property(_prefixHeader ${_target} COTIRE_${_language}_PREFIX_HEADER) + get_target_property(_unityBuildFile ${_target} COTIRE_${_language}_UNITY_SOURCE) + if (_prefixHeader OR _unityBuildFile) + message (STATUS "cotire: target ${_target} has already been cotired.") + set (${_targetLanguagesVar} "" PARENT_SCOPE) + return() + endif() + if (_targetUsePCH AND "${_language}" MATCHES "^C|CXX$") + cotire_check_precompiled_header_support("${_language}" "${_target}" _disableMsg) + if (_disableMsg) + set (_targetUsePCH FALSE) + endif() + endif() + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (_sourceFiles OR _excludedSources OR _cotiredSources) + list (APPEND _targetLanguages ${_language}) + endif() + if (_sourceFiles) + list (APPEND _allSourceFiles ${_sourceFiles}) + endif() + list (LENGTH _sourceFiles _numberOfSources) + if (NOT _numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + list (APPEND _pchEligibleTargetLanguages ${_language}) + endif() + if (_excludedSources) + list (APPEND _allExcludedSourceFiles ${_excludedSources}) + endif() + if (_cotiredSources) + list (APPEND _allCotiredSourceFiles ${_cotiredSources}) + endif() + endforeach() + set (_targetMsgLevel STATUS) + if (NOT _targetLanguages) + string (REPLACE ";" " or " _languagesStr "${_languages}") + set (_disableMsg "No ${_languagesStr} source files.") + set (_targetUsePCH FALSE) + set (_targetAddSCU FALSE) + endif() + if (_targetUsePCH) + if (_allCotiredSourceFiles) + cotire_get_source_file_property_values(_cotireTargets COTIRE_TARGET ${_allCotiredSourceFiles}) + list (REMOVE_DUPLICATES _cotireTargets) + string (REPLACE ";" ", " _cotireTargetsStr "${_cotireTargets}") + set (_disableMsg "Target sources already include a precompiled header for target(s) ${_cotireTargets}.") + set (_disableMsg "${_disableMsg} Set target property COTIRE_ENABLE_PRECOMPILED_HEADER to FALSE for targets ${_target},") + set (_disableMsg "${_disableMsg} ${_cotireTargetsStr} to get a workable build system.") + set (_targetMsgLevel SEND_ERROR) + set (_targetUsePCH FALSE) + elseif (NOT _pchEligibleTargetLanguages) + set (_disableMsg "Too few applicable sources.") + set (_targetUsePCH FALSE) + elseif (XCODE AND _allExcludedSourceFiles) + # for Xcode, we cannot apply the precompiled header to individual sources, only to the whole target + set (_disableMsg "Exclusion of source files not supported for generator Xcode.") + set (_targetUsePCH FALSE) + elseif (XCODE AND "${_targetType}" STREQUAL "OBJECT_LIBRARY") + # for Xcode, we cannot apply the required PRE_BUILD action to generate the prefix header to an OBJECT_LIBRARY target + set (_disableMsg "Required PRE_BUILD action not supported for OBJECT_LIBRARY targets for generator Xcode.") + set (_targetUsePCH FALSE) + endif() + endif() + set_property(TARGET ${_target} PROPERTY COTIRE_ENABLE_PRECOMPILED_HEADER ${_targetUsePCH}) + set_property(TARGET ${_target} PROPERTY COTIRE_ADD_UNITY_BUILD ${_targetAddSCU}) + cotire_make_target_message(${_target} "${_targetLanguages}" "${_disableMsg}" _targetMsg ${_allExcludedSourceFiles}) + if (_targetMsg) + if (NOT DEFINED COTIREMSG_${_target}) + set (COTIREMSG_${_target} "") + endif() + if (COTIRE_VERBOSE OR NOT "${_targetMsgLevel}" STREQUAL "STATUS" OR + NOT "${COTIREMSG_${_target}}" STREQUAL "${_targetMsg}") + # cache message to avoid redundant messages on re-configure + set (COTIREMSG_${_target} "${_targetMsg}" CACHE INTERNAL "${_target} cotire message.") + message (${_targetMsgLevel} "${_targetMsg}") + endif() + endif() + list (LENGTH _targetLanguages _numberOfLanguages) + if (_numberOfLanguages GREATER 1 OR _allExcludedSourceFiles) + set (${_wholeTargetVar} FALSE PARENT_SCOPE) + else() + set (${_wholeTargetVar} TRUE PARENT_SCOPE) + endif() + set (${_targetLanguagesVar} ${_targetLanguages} PARENT_SCOPE) +endfunction() + +function (cotire_compute_unity_max_number_of_includes _target _maxIncludesVar) + set (_sourceFiles ${ARGN}) + get_target_property(_maxIncludes ${_target} COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES) + if (_maxIncludes MATCHES "(-j|--parallel|--jobs) ?([0-9]*)") + set (_numberOfThreads "${CMAKE_MATCH_2}") + if (NOT _numberOfThreads) + # use all available cores + ProcessorCount(_numberOfThreads) + endif() + list (LENGTH _sourceFiles _numberOfSources) + math (EXPR _maxIncludes "(${_numberOfSources} + ${_numberOfThreads} - 1) / ${_numberOfThreads}") + elseif (NOT _maxIncludes MATCHES "[0-9]+") + set (_maxIncludes 0) + endif() + if (COTIRE_DEBUG) + message (STATUS "${_target} unity source max includes: ${_maxIncludes}") + endif() + set (${_maxIncludesVar} ${_maxIncludes} PARENT_SCOPE) +endfunction() + +function (cotire_process_target_language _language _configurations _target _wholeTarget _cmdsVar) + set (${_cmdsVar} "" PARENT_SCOPE) + get_target_property(_targetSourceFiles ${_target} SOURCES) + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (NOT _sourceFiles AND NOT _cotiredSources) + return() + endif() + set (_cmds "") + # check for user provided unity source file list + get_property(_unitySourceFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE_INIT) + if (NOT _unitySourceFiles) + set (_unitySourceFiles ${_sourceFiles} ${_cotiredSources}) + endif() + cotire_generate_target_script( + ${_language} "${_configurations}" ${_target} _targetScript _targetConfigScript ${_unitySourceFiles}) + # set up unity files for parallel compilation + cotire_compute_unity_max_number_of_includes(${_target} _maxIncludes ${_unitySourceFiles}) + cotire_make_unity_source_file_paths(${_language} ${_target} ${_maxIncludes} _unityFiles ${_unitySourceFiles}) + list (LENGTH _unityFiles _numberOfUnityFiles) + if (_numberOfUnityFiles EQUAL 0) + return() + elseif (_numberOfUnityFiles GREATER 1) + cotire_setup_unity_generation_commands( + ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFiles}" _cmds ${_unitySourceFiles}) + endif() + # set up single unity file for prefix header generation + cotire_make_single_unity_source_file_path(${_language} ${_target} _unityFile) + cotire_setup_unity_generation_commands( + ${_language} ${_target} "${_targetScript}" "${_targetConfigScript}" "${_unityFile}" _cmds ${_unitySourceFiles}) + cotire_make_prefix_file_path(${_language} ${_target} _prefixFile) + # set up prefix header + if (_prefixFile) + # check for user provided prefix header files + get_property(_prefixHeaderFiles TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) + if (_prefixHeaderFiles) + cotire_setup_prefix_generation_from_provided_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" _cmds ${_prefixHeaderFiles}) + else() + cotire_setup_prefix_generation_from_unity_command( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_unityFile}" _cmds ${_unitySourceFiles}) + endif() + # check if selected language has enough sources at all + list (LENGTH _sourceFiles _numberOfSources) + if (_numberOfSources LESS ${COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES}) + set (_targetUsePCH FALSE) + else() + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + endif() + if (_targetUsePCH) + cotire_make_pch_file_path(${_language} ${_target} _pchFile) + if (_pchFile) + # first file in _sourceFiles is passed as the host file + cotire_setup_pch_file_compilation( + ${_language} ${_target} "${_targetConfigScript}" "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) + cotire_setup_pch_file_inclusion( + ${_language} ${_target} ${_wholeTarget} "${_prefixFile}" "${_pchFile}" ${_sourceFiles}) + endif() + elseif (_prefixHeaderFiles) + # user provided prefix header must be included unconditionally + cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_sourceFiles}) + endif() + endif() + # mark target as cotired for language + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE "${_unityFiles}") + if (_prefixFile) + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER "${_prefixFile}") + if (_targetUsePCH AND _pchFile) + set_property(TARGET ${_target} PROPERTY COTIRE_${_language}_PRECOMPILED_HEADER "${_pchFile}") + endif() + endif() + set (${_cmdsVar} ${_cmds} PARENT_SCOPE) +endfunction() + +function (cotire_setup_clean_target _target) + set (_cleanTargetName "${_target}${COTIRE_CLEAN_TARGET_SUFFIX}") + if (NOT TARGET "${_cleanTargetName}") + cotire_set_cmd_to_prologue(_cmds) + get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" ABSOLUTE) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${_outputDir}" "${COTIRE_INTDIR}" "${_target}") + add_custom_target(${_cleanTargetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up target ${_target} cotire generated files" + VERBATIM) + cotire_init_target("${_cleanTargetName}") + endif() +endfunction() + +function (cotire_setup_pch_target _languages _configurations _target) + if ("${CMAKE_GENERATOR}" MATCHES "Make|Ninja") + # for makefile based generators, we add a custom target to trigger the generation of the cotire related files + set (_dependsFiles "") + foreach (_language ${_languages}) + set (_props COTIRE_${_language}_PREFIX_HEADER COTIRE_${_language}_UNITY_SOURCE) + if (NOT CMAKE_${_language}_COMPILER_ID MATCHES "MSVC|Intel") + # Visual Studio and Intel only create precompiled header as a side effect + list (INSERT _props 0 COTIRE_${_language}_PRECOMPILED_HEADER) + endif() + cotire_get_first_set_property_value(_dependsFile TARGET ${_target} ${_props}) + if (_dependsFile) + list (APPEND _dependsFiles "${_dependsFile}") + endif() + endforeach() + if (_dependsFiles) + set (_pchTargetName "${_target}${COTIRE_PCH_TARGET_SUFFIX}") + add_custom_target("${_pchTargetName}" DEPENDS ${_dependsFiles}) + cotire_init_target("${_pchTargetName}") + cotire_add_to_pch_all_target(${_pchTargetName}) + endif() + else() + # for other generators, we add the "clean all" target to clean up the precompiled header + cotire_setup_clean_all_target() + endif() +endfunction() + +function (cotire_collect_unity_target_sources _target _languages _unityTargetSourcesVar) + get_target_property(_targetSourceFiles ${_target} SOURCES) + set (_unityTargetSources ${_targetSourceFiles}) + foreach (_language ${_languages}) + get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) + if (_unityFiles) + # remove source files that are included in the unity source + set (_sourceFiles "") + set (_excludedSources "") + set (_cotiredSources "") + cotire_filter_language_source_files(${_language} ${_target} _sourceFiles _excludedSources _cotiredSources ${_targetSourceFiles}) + if (_sourceFiles OR _cotiredSources) + list (REMOVE_ITEM _unityTargetSources ${_sourceFiles} ${_cotiredSources}) + endif() + # add unity source files instead + list (APPEND _unityTargetSources ${_unityFiles}) + endif() + endforeach() + set (${_unityTargetSourcesVar} ${_unityTargetSources} PARENT_SCOPE) +endfunction() + +function (cotire_setup_unity_target_pch_usage _languages _target) + foreach (_language ${_languages}) + get_property(_unityFiles TARGET ${_target} PROPERTY COTIRE_${_language}_UNITY_SOURCE) + if (_unityFiles) + get_property(_userPrefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER_INIT) + get_property(_prefixFile TARGET ${_target} PROPERTY COTIRE_${_language}_PREFIX_HEADER) + if (_userPrefixFile AND _prefixFile) + # user provided prefix header must be included unconditionally by unity sources + cotire_setup_prefix_file_inclusion(${_language} ${_target} "${_prefixFile}" ${_unityFiles}) + endif() + endif() + endforeach() +endfunction() + +function (cotire_setup_unity_build_target _languages _configurations _target) + get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) + if (NOT _unityTargetName) + set (_unityTargetName "${_target}${COTIRE_UNITY_BUILD_TARGET_SUFFIX}") + endif() + # determine unity target sub type + get_target_property(_targetType ${_target} TYPE) + if ("${_targetType}" STREQUAL "EXECUTABLE") + set (_unityTargetSubType "") + elseif (_targetType MATCHES "(STATIC|SHARED|MODULE|OBJECT)_LIBRARY") + set (_unityTargetSubType "${CMAKE_MATCH_1}") + else() + message (WARNING "cotire: target ${_target} has unknown target type ${_targetType}.") + return() + endif() + # determine unity target sources + set (_unityTargetSources "") + cotire_collect_unity_target_sources(${_target} "${_languages}" _unityTargetSources) + # handle automatic Qt processing + get_target_property(_targetAutoMoc ${_target} AUTOMOC) + get_target_property(_targetAutoUic ${_target} AUTOUIC) + get_target_property(_targetAutoRcc ${_target} AUTORCC) + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # if the original target sources are subject to CMake's automatic Qt processing, + # also include implicitly generated _automoc.cpp file + list (APPEND _unityTargetSources "${_target}_automoc.cpp") + set_property (SOURCE "${_target}_automoc.cpp" PROPERTY GENERATED TRUE) + endif() + # prevent AUTOMOC, AUTOUIC and AUTORCC properties from being set when the unity target is created + set (CMAKE_AUTOMOC OFF) + set (CMAKE_AUTOUIC OFF) + set (CMAKE_AUTORCC OFF) + if (COTIRE_DEBUG) + message (STATUS "add target ${_targetType} ${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}") + endif() + # generate unity target + if ("${_targetType}" STREQUAL "EXECUTABLE") + add_executable(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) + else() + add_library(${_unityTargetName} ${_unityTargetSubType} EXCLUDE_FROM_ALL ${_unityTargetSources}) + endif() + if (_targetAutoMoc OR _targetAutoUic OR _targetAutoRcc) + # depend on the original target's implicity generated _automoc target + add_dependencies(${_unityTargetName} ${_target}_automoc) + endif() + # copy output location properties + set (_outputDirProperties + ARCHIVE_OUTPUT_DIRECTORY ARCHIVE_OUTPUT_DIRECTORY_ + LIBRARY_OUTPUT_DIRECTORY LIBRARY_OUTPUT_DIRECTORY_ + RUNTIME_OUTPUT_DIRECTORY RUNTIME_OUTPUT_DIRECTORY_) + if (COTIRE_UNITY_OUTPUT_DIRECTORY) + set (_setDefaultOutputDir TRUE) + if (IS_ABSOLUTE "${COTIRE_UNITY_OUTPUT_DIRECTORY}") + set (_outputDir "${COTIRE_UNITY_OUTPUT_DIRECTORY}") + else() + # append relative COTIRE_UNITY_OUTPUT_DIRECTORY to target's actual output directory + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} ${_outputDirProperties}) + cotire_resolve_config_properites("${_configurations}" _properties ${_outputDirProperties}) + foreach (_property ${_properties}) + get_property(_outputDir TARGET ${_target} PROPERTY ${_property}) + if (_outputDir) + get_filename_component(_outputDir "${_outputDir}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) + set_property(TARGET ${_unityTargetName} PROPERTY ${_property} "${_outputDir}") + set (_setDefaultOutputDir FALSE) + endif() + endforeach() + if (_setDefaultOutputDir) + get_filename_component(_outputDir "${CMAKE_CURRENT_BINARY_DIR}/${COTIRE_UNITY_OUTPUT_DIRECTORY}" ABSOLUTE) + endif() + endif() + if (_setDefaultOutputDir) + set_target_properties(${_unityTargetName} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${_outputDir}" + LIBRARY_OUTPUT_DIRECTORY "${_outputDir}" + RUNTIME_OUTPUT_DIRECTORY "${_outputDir}") + endif() + else() + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ${_outputDirProperties}) + endif() + # copy output name + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ARCHIVE_OUTPUT_NAME ARCHIVE_OUTPUT_NAME_ + LIBRARY_OUTPUT_NAME LIBRARY_OUTPUT_NAME_ + OUTPUT_NAME OUTPUT_NAME_ + RUNTIME_OUTPUT_NAME RUNTIME_OUTPUT_NAME_ + PREFIX _POSTFIX SUFFIX + IMPORT_PREFIX IMPORT_SUFFIX) + # copy compile stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + COMPILE_DEFINITIONS COMPILE_DEFINITIONS_ + COMPILE_FLAGS COMPILE_OPTIONS + Fortran_FORMAT Fortran_MODULE_DIRECTORY + INCLUDE_DIRECTORIES + INTERPROCEDURAL_OPTIMIZATION INTERPROCEDURAL_OPTIMIZATION_ + POSITION_INDEPENDENT_CODE + C_COMPILER_LAUNCHER CXX_COMPILER_LAUNCHER + C_INCLUDE_WHAT_YOU_USE CXX_INCLUDE_WHAT_YOU_USE + C_VISIBILITY_PRESET CXX_VISIBILITY_PRESET VISIBILITY_INLINES_HIDDEN) + # copy compile features + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + C_EXTENSIONS C_STANDARD C_STANDARD_REQUIRED + CXX_EXTENSIONS CXX_STANDARD CXX_STANDARD_REQUIRED + COMPILE_FEATURES) + # copy interface stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + COMPATIBLE_INTERFACE_BOOL COMPATIBLE_INTERFACE_NUMBER_MAX COMPATIBLE_INTERFACE_NUMBER_MIN + COMPATIBLE_INTERFACE_STRING + INTERFACE_COMPILE_DEFINITIONS INTERFACE_COMPILE_FEATURES INTERFACE_COMPILE_OPTIONS + INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SOURCES + INTERFACE_POSITION_INDEPENDENT_CODE INTERFACE_SYSTEM_INCLUDE_DIRECTORIES + INTERFACE_AUTOUIC_OPTIONS NO_SYSTEM_FROM_IMPORTED) + # copy link stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + BUILD_WITH_INSTALL_RPATH INSTALL_RPATH INSTALL_RPATH_USE_LINK_PATH SKIP_BUILD_RPATH + LINKER_LANGUAGE LINK_DEPENDS LINK_DEPENDS_NO_SHARED + LINK_FLAGS LINK_FLAGS_ + LINK_INTERFACE_LIBRARIES LINK_INTERFACE_LIBRARIES_ + LINK_INTERFACE_MULTIPLICITY LINK_INTERFACE_MULTIPLICITY_ + LINK_SEARCH_START_STATIC LINK_SEARCH_END_STATIC + STATIC_LIBRARY_FLAGS STATIC_LIBRARY_FLAGS_ + NO_SONAME SOVERSION VERSION) + # copy cmake stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + IMPLICIT_DEPENDS_INCLUDE_TRANSFORM RULE_LAUNCH_COMPILE RULE_LAUNCH_CUSTOM RULE_LAUNCH_LINK) + # copy Apple platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + BUNDLE BUNDLE_EXTENSION FRAMEWORK FRAMEWORK_VERSION INSTALL_NAME_DIR + MACOSX_BUNDLE MACOSX_BUNDLE_INFO_PLIST MACOSX_FRAMEWORK_INFO_PLIST MACOSX_RPATH + OSX_ARCHITECTURES OSX_ARCHITECTURES_ PRIVATE_HEADER PUBLIC_HEADER RESOURCE XCTEST) + # copy Windows platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + GNUtoMS + COMPILE_PDB_NAME COMPILE_PDB_NAME_ + COMPILE_PDB_OUTPUT_DIRECTORY COMPILE_PDB_OUTPUT_DIRECTORY_ + PDB_NAME PDB_NAME_ PDB_OUTPUT_DIRECTORY PDB_OUTPUT_DIRECTORY_ + VS_DESKTOP_EXTENSIONS_VERSION VS_DOTNET_REFERENCES VS_DOTNET_TARGET_FRAMEWORK_VERSION + VS_GLOBAL_KEYWORD VS_GLOBAL_PROJECT_TYPES VS_GLOBAL_ROOTNAMESPACE + VS_IOT_EXTENSIONS_VERSION VS_IOT_STARTUP_TASK + VS_KEYWORD VS_MOBILE_EXTENSIONS_VERSION + VS_SCC_AUXPATH VS_SCC_LOCALPATH VS_SCC_PROJECTNAME VS_SCC_PROVIDER + VS_WINDOWS_TARGET_PLATFORM_MIN_VERSION + VS_WINRT_COMPONENT VS_WINRT_EXTENSIONS VS_WINRT_REFERENCES + WIN32_EXECUTABLE WINDOWS_EXPORT_ALL_SYMBOLS) + # copy Android platform specific stuff + cotire_copy_set_properites("${_configurations}" TARGET ${_target} ${_unityTargetName} + ANDROID_API ANDROID_API_MIN ANDROID_GUI + ANDROID_ANT_ADDITIONAL_OPTIONS ANDROID_ARCH ANDROID_ASSETS_DIRECTORIES + ANDROID_JAR_DEPENDENCIES ANDROID_JAR_DIRECTORIES ANDROID_JAVA_SOURCE_DIR + ANDROID_NATIVE_LIB_DEPENDENCIES ANDROID_NATIVE_LIB_DIRECTORIES + ANDROID_PROCESS_MAX ANDROID_PROGUARD ANDROID_PROGUARD_CONFIG_PATH + ANDROID_SECURE_PROPS_PATH ANDROID_SKIP_ANT_STEP ANDROID_STL_TYPE) + # use output name from original target + get_target_property(_targetOutputName ${_unityTargetName} OUTPUT_NAME) + if (NOT _targetOutputName) + set_property(TARGET ${_unityTargetName} PROPERTY OUTPUT_NAME "${_target}") + endif() + # use export symbol from original target + cotire_get_target_export_symbol("${_target}" _defineSymbol) + if (_defineSymbol) + set_property(TARGET ${_unityTargetName} PROPERTY DEFINE_SYMBOL "${_defineSymbol}") + if ("${_targetType}" STREQUAL "EXECUTABLE") + set_property(TARGET ${_unityTargetName} PROPERTY ENABLE_EXPORTS TRUE) + endif() + endif() + cotire_init_target(${_unityTargetName}) + cotire_add_to_unity_all_target(${_unityTargetName}) + set_property(TARGET ${_target} PROPERTY COTIRE_UNITY_TARGET_NAME "${_unityTargetName}") +endfunction(cotire_setup_unity_build_target) + +function (cotire_target _target) + set(_options "") + set(_oneValueArgs "") + set(_multiValueArgs LANGUAGES CONFIGURATIONS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if (NOT _option_LANGUAGES) + get_property (_option_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + endif() + if (NOT _option_CONFIGURATIONS) + cotire_get_configuration_types(_option_CONFIGURATIONS) + endif() + # trivial checks + get_target_property(_imported ${_target} IMPORTED) + if (_imported) + message (WARNING "cotire: imported target ${_target} cannot be cotired.") + return() + endif() + # resolve alias + get_target_property(_aliasName ${_target} ALIASED_TARGET) + if (_aliasName) + if (COTIRE_DEBUG) + message (STATUS "${_target} is an alias. Applying cotire to aliased target ${_aliasName} instead.") + endif() + set (_target ${_aliasName}) + endif() + # check if target needs to be cotired for build type + # when using configuration types, the test is performed at build time + cotire_init_cotire_target_properties(${_target}) + if (NOT CMAKE_CONFIGURATION_TYPES) + if (CMAKE_BUILD_TYPE) + list (FIND _option_CONFIGURATIONS "${CMAKE_BUILD_TYPE}" _index) + else() + list (FIND _option_CONFIGURATIONS "None" _index) + endif() + if (_index EQUAL -1) + if (COTIRE_DEBUG) + message (STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} not cotired (${_option_CONFIGURATIONS})") + endif() + return() + endif() + endif() + # when not using configuration types, immediately create cotire intermediate dir + if (NOT CMAKE_CONFIGURATION_TYPES) + cotire_get_intermediate_dir(_baseDir) + file (MAKE_DIRECTORY "${_baseDir}") + endif() + # choose languages that apply to the target + cotire_choose_target_languages("${_target}" _targetLanguages _wholeTarget ${_option_LANGUAGES}) + if (NOT _targetLanguages) + return() + endif() + set (_cmds "") + foreach (_language ${_targetLanguages}) + cotire_process_target_language("${_language}" "${_option_CONFIGURATIONS}" ${_target} ${_wholeTarget} _cmd) + if (_cmd) + list (APPEND _cmds ${_cmd}) + endif() + endforeach() + get_target_property(_targetAddSCU ${_target} COTIRE_ADD_UNITY_BUILD) + if (_targetAddSCU) + cotire_setup_unity_build_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) + endif() + get_target_property(_targetUsePCH ${_target} COTIRE_ENABLE_PRECOMPILED_HEADER) + if (_targetUsePCH) + cotire_setup_target_pch_usage("${_targetLanguages}" ${_target} ${_wholeTarget} ${_cmds}) + cotire_setup_pch_target("${_targetLanguages}" "${_option_CONFIGURATIONS}" ${_target}) + if (_targetAddSCU) + cotire_setup_unity_target_pch_usage("${_targetLanguages}" ${_target}) + endif() + endif() + get_target_property(_targetAddCleanTarget ${_target} COTIRE_ADD_CLEAN) + if (_targetAddCleanTarget) + cotire_setup_clean_target(${_target}) + endif() +endfunction(cotire_target) + +function (cotire_map_libraries _strategy _mappedLibrariesVar) + set (_mappedLibraries "") + foreach (_library ${ARGN}) + if (TARGET "${_library}" AND "${_strategy}" MATCHES "COPY_UNITY") + # use target's corresponding unity target, if available + get_target_property(_libraryUnityTargetName ${_library} COTIRE_UNITY_TARGET_NAME) + if (TARGET "${_libraryUnityTargetName}") + list (APPEND _mappedLibraries "${_libraryUnityTargetName}") + else() + list (APPEND _mappedLibraries "${_library}") + endif() + else() + list (APPEND _mappedLibraries "${_library}") + endif() + endforeach() + list (REMOVE_DUPLICATES _mappedLibraries) + set (${_mappedLibrariesVar} ${_mappedLibraries} PARENT_SCOPE) +endfunction() + +function (cotire_target_link_libraries _target) + get_target_property(_unityTargetName ${_target} COTIRE_UNITY_TARGET_NAME) + if (TARGET "${_unityTargetName}") + get_target_property(_linkLibrariesStrategy ${_target} COTIRE_UNITY_LINK_LIBRARIES_INIT) + if (COTIRE_DEBUG) + message (STATUS "unity target ${_unityTargetName} link strategy: ${_linkLibrariesStrategy}") + endif() + if ("${_linkLibrariesStrategy}" MATCHES "^(COPY|COPY_UNITY)$") + set (_unityLinkLibraries "") + get_target_property(_linkLibraries ${_target} LINK_LIBRARIES) + if (_linkLibraries) + list (APPEND _unityLinkLibraries ${_linkLibraries}) + endif() + get_target_property(_interfaceLinkLibraries ${_target} INTERFACE_LINK_LIBRARIES) + if (_interfaceLinkLibraries) + list (APPEND _unityLinkLibraries ${_interfaceLinkLibraries}) + endif() + cotire_map_libraries("${_linkLibrariesStrategy}" _unityLinkLibraries ${_unityLinkLibraries}) + if (COTIRE_DEBUG) + message (STATUS "unity target ${_unityTargetName} libraries: ${_unityLinkLibraries}") + endif() + if (_unityLinkLibraries) + target_link_libraries(${_unityTargetName} ${_unityLinkLibraries}) + endif() + endif() + endif() +endfunction(cotire_target_link_libraries) + +function (cotire_cleanup _binaryDir _cotireIntermediateDirName _targetName) + if (_targetName) + file (GLOB_RECURSE _cotireFiles "${_binaryDir}/${_targetName}*.*") + else() + file (GLOB_RECURSE _cotireFiles "${_binaryDir}/*.*") + endif() + # filter files in intermediate directory + set (_filesToRemove "") + foreach (_file ${_cotireFiles}) + get_filename_component(_dir "${_file}" DIRECTORY) + get_filename_component(_dirName "${_dir}" NAME) + if ("${_dirName}" STREQUAL "${_cotireIntermediateDirName}") + list (APPEND _filesToRemove "${_file}") + endif() + endforeach() + if (_filesToRemove) + if (COTIRE_VERBOSE) + message (STATUS "cleaning up ${_filesToRemove}") + endif() + file (REMOVE ${_filesToRemove}) + endif() +endfunction() + +function (cotire_init_target _targetName) + if (COTIRE_TARGETS_FOLDER) + set_target_properties(${_targetName} PROPERTIES FOLDER "${COTIRE_TARGETS_FOLDER}") + endif() + set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_ALL TRUE) + if (MSVC_IDE) + set_target_properties(${_targetName} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE) + endif() +endfunction() + +function (cotire_add_to_pch_all_target _pchTargetName) + set (_targetName "${COTIRE_PCH_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + cotire_init_target("${_targetName}") + endif() + cotire_setup_clean_all_target() + add_dependencies(${_targetName} ${_pchTargetName}) +endfunction() + +function (cotire_add_to_unity_all_target _unityTargetName) + set (_targetName "${COTIRE_UNITY_BUILD_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + add_custom_target("${_targetName}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + VERBATIM) + cotire_init_target("${_targetName}") + endif() + cotire_setup_clean_all_target() + add_dependencies(${_targetName} ${_unityTargetName}) +endfunction() + +function (cotire_setup_clean_all_target) + set (_targetName "${COTIRE_CLEAN_ALL_TARGET_NAME}") + if (NOT TARGET "${_targetName}") + cotire_set_cmd_to_prologue(_cmds) + list (APPEND _cmds -P "${COTIRE_CMAKE_MODULE_FILE}" "cleanup" "${CMAKE_BINARY_DIR}" "${COTIRE_INTDIR}") + add_custom_target(${_targetName} + COMMAND ${_cmds} + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Cleaning up all cotire generated files" + VERBATIM) + cotire_init_target("${_targetName}") + endif() +endfunction() + +function (cotire) + set(_options "") + set(_oneValueArgs "") + set(_multiValueArgs LANGUAGES CONFIGURATIONS) + cmake_parse_arguments(_option "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + set (_targets ${_option_UNPARSED_ARGUMENTS}) + foreach (_target ${_targets}) + if (TARGET ${_target}) + cotire_target(${_target} LANGUAGES ${_option_LANGUAGES} CONFIGURATIONS ${_option_CONFIGURATIONS}) + else() + message (WARNING "cotire: ${_target} is not a target.") + endif() + endforeach() + foreach (_target ${_targets}) + if (TARGET ${_target}) + cotire_target_link_libraries(${_target}) + endif() + endforeach() +endfunction() + +if (CMAKE_SCRIPT_MODE_FILE) + + # cotire is being run in script mode + # locate -P on command args + set (COTIRE_ARGC -1) + foreach (_index RANGE ${CMAKE_ARGC}) + if (COTIRE_ARGC GREATER -1) + set (COTIRE_ARGV${COTIRE_ARGC} "${CMAKE_ARGV${_index}}") + math (EXPR COTIRE_ARGC "${COTIRE_ARGC} + 1") + elseif ("${CMAKE_ARGV${_index}}" STREQUAL "-P") + set (COTIRE_ARGC 0) + endif() + endforeach() + + # include target script if available + if ("${COTIRE_ARGV2}" MATCHES "\\.cmake$") + # the included target scripts sets up additional variables relating to the target (e.g., COTIRE_TARGET_SOURCES) + include("${COTIRE_ARGV2}") + endif() + + if (COTIRE_DEBUG) + message (STATUS "${COTIRE_ARGV0} ${COTIRE_ARGV1} ${COTIRE_ARGV2} ${COTIRE_ARGV3} ${COTIRE_ARGV4} ${COTIRE_ARGV5}") + endif() + + if (NOT COTIRE_BUILD_TYPE) + set (COTIRE_BUILD_TYPE "None") + endif() + string (TOUPPER "${COTIRE_BUILD_TYPE}" _upperConfig) + set (_includeDirs ${COTIRE_TARGET_INCLUDE_DIRECTORIES_${_upperConfig}}) + set (_systemIncludeDirs ${COTIRE_TARGET_SYSTEM_INCLUDE_DIRECTORIES_${_upperConfig}}) + set (_compileDefinitions ${COTIRE_TARGET_COMPILE_DEFINITIONS_${_upperConfig}}) + set (_compileFlags ${COTIRE_TARGET_COMPILE_FLAGS_${_upperConfig}}) + # check if target has been cotired for actual build type COTIRE_BUILD_TYPE + list (FIND COTIRE_TARGET_CONFIGURATION_TYPES "${COTIRE_BUILD_TYPE}" _index) + if (_index GREATER -1) + set (_sources ${COTIRE_TARGET_SOURCES}) + set (_sourcesDefinitions ${COTIRE_TARGET_SOURCES_COMPILE_DEFINITIONS_${_upperConfig}}) + else() + if (COTIRE_DEBUG) + message (STATUS "COTIRE_BUILD_TYPE=${COTIRE_BUILD_TYPE} not cotired (${COTIRE_TARGET_CONFIGURATION_TYPES})") + endif() + set (_sources "") + set (_sourcesDefinitions "") + endif() + set (_targetPreUndefs ${COTIRE_TARGET_PRE_UNDEFS}) + set (_targetPostUndefs ${COTIRE_TARGET_POST_UNDEFS}) + set (_sourcesPreUndefs ${COTIRE_TARGET_SOURCES_PRE_UNDEFS}) + set (_sourcesPostUndefs ${COTIRE_TARGET_SOURCES_POST_UNDEFS}) + + if ("${COTIRE_ARGV1}" STREQUAL "unity") + + if (XCODE) + # executing pre-build action under Xcode, check dependency on target script + set (_dependsOption DEPENDS "${COTIRE_ARGV2}") + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + cotire_select_unity_source_files("${COTIRE_ARGV3}" _sources ${_sources}) + + cotire_generate_unity_source( + "${COTIRE_ARGV3}" ${_sources} + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + SOURCES_COMPILE_DEFINITIONS ${_sourcesDefinitions} + PRE_UNDEFS ${_targetPreUndefs} + POST_UNDEFS ${_targetPostUndefs} + SOURCES_PRE_UNDEFS ${_sourcesPreUndefs} + SOURCES_POST_UNDEFS ${_sourcesPostUndefs} + ${_dependsOption}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "prefix") + + if (XCODE) + # executing pre-build action under Xcode, check dependency on unity file and prefix dependencies + set (_dependsOption DEPENDS "${COTIRE_ARGV4}" ${COTIRE_TARGET_PREFIX_DEPENDS}) + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + set (_files "") + foreach (_index RANGE 4 ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + cotire_generate_prefix_header( + "${COTIRE_ARGV3}" ${_files} + COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" + COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} + COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + IGNORE_PATH "${COTIRE_TARGET_IGNORE_PATH};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH}" + INCLUDE_PATH ${COTIRE_TARGET_INCLUDE_PATH} + IGNORE_EXTENSIONS "${CMAKE_${COTIRE_TARGET_LANGUAGE}_SOURCE_FILE_EXTENSIONS};${COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS}" + INCLUDE_PRIORITY_PATH ${COTIRE_TARGET_INCLUDE_PRIORITY_PATH} + INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} + COMPILE_DEFINITIONS ${_compileDefinitions} + COMPILE_FLAGS ${_compileFlags} + ${_dependsOption}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "precompile") + + set (_files "") + foreach (_index RANGE 5 ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + cotire_precompile_prefix_header( + "${COTIRE_ARGV3}" "${COTIRE_ARGV4}" "${COTIRE_ARGV5}" + COMPILER_EXECUTABLE "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER}" + COMPILER_ARG1 ${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ARG1} + COMPILER_ID "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_ID}" + COMPILER_VERSION "${CMAKE_${COTIRE_TARGET_LANGUAGE}_COMPILER_VERSION}" + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + INCLUDE_DIRECTORIES ${_includeDirs} + SYSTEM_INCLUDE_DIRECTORIES ${_systemIncludeDirs} + COMPILE_DEFINITIONS ${_compileDefinitions} + COMPILE_FLAGS ${_compileFlags}) + + elseif ("${COTIRE_ARGV1}" STREQUAL "combine") + + if (COTIRE_TARGET_LANGUAGE) + set (_combinedFile "${COTIRE_ARGV3}") + set (_startIndex 4) + else() + set (_combinedFile "${COTIRE_ARGV2}") + set (_startIndex 3) + endif() + set (_files "") + foreach (_index RANGE ${_startIndex} ${COTIRE_ARGC}) + if (COTIRE_ARGV${_index}) + list (APPEND _files "${COTIRE_ARGV${_index}}") + endif() + endforeach() + + if (XCODE) + # executing pre-build action under Xcode, check dependency on files to be combined + set (_dependsOption DEPENDS ${_files}) + else() + # executing custom command, no need to re-check for dependencies + set (_dependsOption "") + endif() + + if (COTIRE_TARGET_LANGUAGE) + cotire_generate_unity_source( + "${_combinedFile}" ${_files} + LANGUAGE "${COTIRE_TARGET_LANGUAGE}" + ${_dependsOption}) + else() + cotire_generate_unity_source("${_combinedFile}" ${_files} ${_dependsOption}) + endif() + + elseif ("${COTIRE_ARGV1}" STREQUAL "cleanup") + + cotire_cleanup("${COTIRE_ARGV2}" "${COTIRE_ARGV3}" "${COTIRE_ARGV4}") + + else() + message (FATAL_ERROR "cotire: unknown command \"${COTIRE_ARGV1}\".") + endif() + +else() + + # cotire is being run in include mode + # set up all variable and property definitions + + if (NOT DEFINED COTIRE_DEBUG_INIT) + if (DEFINED COTIRE_DEBUG) + set (COTIRE_DEBUG_INIT ${COTIRE_DEBUG}) + else() + set (COTIRE_DEBUG_INIT FALSE) + endif() + endif() + option (COTIRE_DEBUG "Enable cotire debugging output?" ${COTIRE_DEBUG_INIT}) + + if (NOT DEFINED COTIRE_VERBOSE_INIT) + if (DEFINED COTIRE_VERBOSE) + set (COTIRE_VERBOSE_INIT ${COTIRE_VERBOSE}) + else() + set (COTIRE_VERBOSE_INIT FALSE) + endif() + endif() + option (COTIRE_VERBOSE "Enable cotire verbose output?" ${COTIRE_VERBOSE_INIT}) + + set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS "inc;inl;ipp" CACHE STRING + "Ignore headers with the listed file extensions from the generated prefix header.") + + set (COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH "" CACHE STRING + "Ignore headers from these directories when generating the prefix header.") + + set (COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS "m;mm" CACHE STRING + "Ignore sources with the listed file extensions from the generated unity source.") + + set (COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES "3" CACHE STRING + "Minimum number of sources in target required to enable use of precompiled header.") + + if (NOT DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT) + if (DEFINED COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES) + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT ${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES}) + elseif ("${CMAKE_GENERATOR}" MATCHES "JOM|Ninja|Visual Studio") + # enable parallelization for generators that run multiple jobs by default + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "-j") + else() + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT "0") + endif() + endif() + set (COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES "${COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES_INIT}" CACHE STRING + "Maximum number of source files to include in a single unity source file.") + + if (NOT COTIRE_PREFIX_HEADER_FILENAME_SUFFIX) + set (COTIRE_PREFIX_HEADER_FILENAME_SUFFIX "_prefix") + endif() + if (NOT COTIRE_UNITY_SOURCE_FILENAME_SUFFIX) + set (COTIRE_UNITY_SOURCE_FILENAME_SUFFIX "_unity") + endif() + if (NOT COTIRE_INTDIR) + set (COTIRE_INTDIR "cotire") + endif() + if (NOT COTIRE_PCH_ALL_TARGET_NAME) + set (COTIRE_PCH_ALL_TARGET_NAME "all_pch") + endif() + if (NOT COTIRE_UNITY_BUILD_ALL_TARGET_NAME) + set (COTIRE_UNITY_BUILD_ALL_TARGET_NAME "all_unity") + endif() + if (NOT COTIRE_CLEAN_ALL_TARGET_NAME) + set (COTIRE_CLEAN_ALL_TARGET_NAME "clean_cotire") + endif() + if (NOT COTIRE_CLEAN_TARGET_SUFFIX) + set (COTIRE_CLEAN_TARGET_SUFFIX "_clean_cotire") + endif() + if (NOT COTIRE_PCH_TARGET_SUFFIX) + set (COTIRE_PCH_TARGET_SUFFIX "_pch") + endif() + if (MSVC) + # MSVC default PCH memory scaling factor of 100 percent (75 MB) is too small for template heavy C++ code + # use a bigger default factor of 170 percent (128 MB) + if (NOT DEFINED COTIRE_PCH_MEMORY_SCALING_FACTOR) + set (COTIRE_PCH_MEMORY_SCALING_FACTOR "170") + endif() + endif() + if (NOT COTIRE_UNITY_BUILD_TARGET_SUFFIX) + set (COTIRE_UNITY_BUILD_TARGET_SUFFIX "_unity") + endif() + if (NOT DEFINED COTIRE_TARGETS_FOLDER) + set (COTIRE_TARGETS_FOLDER "cotire") + endif() + if (NOT DEFINED COTIRE_UNITY_OUTPUT_DIRECTORY) + if ("${CMAKE_GENERATOR}" MATCHES "Ninja") + # generated Ninja build files do not work if the unity target produces the same output file as the cotired target + set (COTIRE_UNITY_OUTPUT_DIRECTORY "unity") + else() + set (COTIRE_UNITY_OUTPUT_DIRECTORY "") + endif() + endif() + + # define cotire cache variables + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_PATH" + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "The variable can be set to a semicolon separated list of include directories." + "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." + "If not defined, defaults to empty list." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_ADDITIONAL_PREFIX_HEADER_IGNORE_EXTENSIONS" + BRIEF_DOCS "Ignore includes with the listed file extensions from the generated prefix header." + FULL_DOCS + "The variable can be set to a semicolon separated list of file extensions." + "If a header file extension matches one in the list, it will be excluded from the generated prefix header." + "Includes with an extension in CMAKE__SOURCE_FILE_EXTENSIONS are always ignored." + "If not defined, defaults to inc;inl;ipp." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_UNITY_SOURCE_EXCLUDE_EXTENSIONS" + BRIEF_DOCS "Exclude sources with the listed file extensions from the generated unity source." + FULL_DOCS + "The variable can be set to a semicolon separated list of file extensions." + "If a source file extension matches one in the list, it will be excluded from the generated unity source file." + "Source files with an extension in CMAKE__IGNORE_EXTENSIONS are always excluded." + "If not defined, defaults to m;mm." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_MINIMUM_NUMBER_OF_TARGET_SOURCES" + BRIEF_DOCS "Minimum number of sources in target required to enable use of precompiled header." + FULL_DOCS + "The variable can be set to an integer > 0." + "If a target contains less than that number of source files, cotire will not enable the use of the precompiled header for the target." + "If not defined, defaults to 3." + ) + + define_property( + CACHED_VARIABLE PROPERTY "COTIRE_MAXIMUM_NUMBER_OF_UNITY_INCLUDES" + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "This may be set to an integer >= 0." + "If 0, cotire will only create a single unity source file." + "If a target contains more than that number of source files, cotire will create multiple unity source files for it." + "Can be set to \"-j\" to optimize the count of unity source files for the number of available processor cores." + "Can be set to \"-j jobs\" to optimize the number of unity source files for the given number of simultaneous jobs." + "Is used to initialize the target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." + "Defaults to \"-j\" for the generators Visual Studio, JOM or Ninja. Defaults to 0 otherwise." + ) + + # define cotire directory properties + + define_property( + DIRECTORY PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" + BRIEF_DOCS "Modify build command of cotired targets added in this directory to make use of the generated precompiled header." + FULL_DOCS + "See target property COTIRE_ENABLE_PRECOMPILED_HEADER." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_ADD_UNITY_BUILD" + BRIEF_DOCS "Add a new target that performs a unity build for cotired targets added in this directory." + FULL_DOCS + "See target property COTIRE_ADD_UNITY_BUILD." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_ADD_CLEAN" + BRIEF_DOCS "Add a new target that cleans all cotire generated files for cotired targets added in this directory." + FULL_DOCS + "See target property COTIRE_ADD_CLEAN." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_IGNORE_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" + BRIEF_DOCS "Honor headers from these directories when generating the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_INCLUDE_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" + BRIEF_DOCS "Header paths matching one of these directories are put at the top of the prefix header." + FULL_DOCS + "See target property COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_PRE_UNDEFS." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_POST_UNDEFS." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "See target property COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES." + ) + + define_property( + DIRECTORY PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" + BRIEF_DOCS "Define strategy for setting up the unity target's link libraries." + FULL_DOCS + "See target property COTIRE_UNITY_LINK_LIBRARIES_INIT." + ) + + # define cotire target properties + + define_property( + TARGET PROPERTY "COTIRE_ENABLE_PRECOMPILED_HEADER" INHERITED + BRIEF_DOCS "Modify this target's build command to make use of the generated precompiled header." + FULL_DOCS + "If this property is set to TRUE, cotire will modify the build command to make use of the generated precompiled header." + "Irrespective of the value of this property, cotire will setup custom commands to generate the unity source and prefix header for the target." + "For makefile based generators cotire will also set up a custom target to manually invoke the generation of the precompiled header." + "The target name will be set to this target's name with the suffix _pch appended." + "Inherited from directory." + "Defaults to TRUE." + ) + + define_property( + TARGET PROPERTY "COTIRE_ADD_UNITY_BUILD" INHERITED + BRIEF_DOCS "Add a new target that performs a unity build for this target." + FULL_DOCS + "If this property is set to TRUE, cotire creates a new target of the same type that uses the generated unity source file instead of the target sources." + "Most of the relevant target properties will be copied from this target to the new unity build target." + "Target dependencies and linked libraries have to be manually set up for the new unity build target." + "The unity target name will be set to this target's name with the suffix _unity appended." + "Inherited from directory." + "Defaults to TRUE." + ) + + define_property( + TARGET PROPERTY "COTIRE_ADD_CLEAN" INHERITED + BRIEF_DOCS "Add a new target that cleans all cotire generated files for this target." + FULL_DOCS + "If this property is set to TRUE, cotire creates a new target that clean all files (unity source, prefix header, precompiled header)." + "The clean target name will be set to this target's name with the suffix _clean_cotire appended." + "Inherited from directory." + "Defaults to FALSE." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_IGNORE_PATH" INHERITED + BRIEF_DOCS "Ignore headers from these directories when generating the prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "If a header file is found in one of these directories or sub-directories, it will be excluded from the generated prefix header." + "Inherited from directory." + "If not set, this property is initialized to \${CMAKE_SOURCE_DIR};\${CMAKE_BINARY_DIR}." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PATH" INHERITED + BRIEF_DOCS "Honor headers from these directories when generating the prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "If a header file is found in one of these directories or sub-directories, it will be included in the generated prefix header." + "If a header file is both selected by COTIRE_PREFIX_HEADER_IGNORE_PATH and COTIRE_PREFIX_HEADER_INCLUDE_PATH," + "the option which yields the closer relative path match wins." + "Inherited from directory." + "If not set, this property is initialized to the empty list." + ) + + define_property( + TARGET PROPERTY "COTIRE_PREFIX_HEADER_INCLUDE_PRIORITY_PATH" INHERITED + BRIEF_DOCS "Header paths matching one of these directories are put at the top of prefix header." + FULL_DOCS + "The property can be set to a list of directories." + "Header file paths matching one of these directories will be inserted at the beginning of the generated prefix header." + "Header files are sorted according to the order of the directories in the property." + "If not set, this property is initialized to the empty list." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" INHERITED + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of each target source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file before each target source file." + "Inherited from directory." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" INHERITED + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of each target source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file after each target source file." + "Inherited from directory." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES" INHERITED + BRIEF_DOCS "Maximum number of source files to include in a single unity source file." + FULL_DOCS + "This may be set to an integer > 0." + "If a target contains more than that number of source files, cotire will create multiple unity build files for it." + "If not set, cotire will only create a single unity source file." + "Inherited from directory." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__UNITY_SOURCE_INIT" + BRIEF_DOCS "User provided unity source file to be used instead of the automatically generated one." + FULL_DOCS + "If set, cotire will only add the given file(s) to the generated unity source file." + "If not set, cotire will add all the target source files to the generated unity source file." + "The property can be set to a user provided unity source file." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__PREFIX_HEADER_INIT" + BRIEF_DOCS "User provided prefix header file to be used instead of the automatically generated one." + FULL_DOCS + "If set, cotire will add the given header file(s) to the generated prefix header file." + "If not set, cotire will generate a prefix header by tracking the header files included by the unity source file." + "The property can be set to a user provided prefix header file (e.g., stdafx.h)." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_LINK_LIBRARIES_INIT" INHERITED + BRIEF_DOCS "Define strategy for setting up unity target's link libraries." + FULL_DOCS + "If this property is empty or set to NONE, the generated unity target's link libraries have to be set up manually." + "If this property is set to COPY, the unity target's link libraries will be copied from this target." + "If this property is set to COPY_UNITY, the unity target's link libraries will be copied from this target with considering existing unity targets." + "Inherited from directory." + "Defaults to empty." + ) + + define_property( + TARGET PROPERTY "COTIRE__UNITY_SOURCE" + BRIEF_DOCS "Read-only property. The generated unity source file(s)." + FULL_DOCS + "cotire sets this property to the path of the generated single computation unit source file for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE__PREFIX_HEADER" + BRIEF_DOCS "Read-only property. The generated prefix header file." + FULL_DOCS + "cotire sets this property to the full path of the generated language prefix header for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE__PRECOMPILED_HEADER" + BRIEF_DOCS "Read-only property. The generated precompiled header file." + FULL_DOCS + "cotire sets this property to the full path of the generated language precompiled header binary for the target." + "Defaults to empty string." + ) + + define_property( + TARGET PROPERTY "COTIRE_UNITY_TARGET_NAME" + BRIEF_DOCS "The name of the generated unity build target corresponding to this target." + FULL_DOCS + "This property can be set to the desired name of the unity target that will be created by cotire." + "If not set, the unity target name will be set to this target's name with the suffix _unity appended." + "After this target has been processed by cotire, the property is set to the actual name of the generated unity target." + "Defaults to empty string." + ) + + # define cotire source properties + + define_property( + SOURCE PROPERTY "COTIRE_EXCLUDED" + BRIEF_DOCS "Do not modify source file's build command." + FULL_DOCS + "If this property is set to TRUE, the source file's build command will not be modified to make use of the precompiled header." + "The source file will also be excluded from the generated unity source file." + "Source files that have their COMPILE_FLAGS property set will be excluded by default." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_DEPENDENCY" + BRIEF_DOCS "Add this source file to dependencies of the automatically generated prefix header file." + FULL_DOCS + "If this property is set to TRUE, the source file is added to dependencies of the generated prefix header file." + "If the file is modified, cotire will re-generate the prefix header source upon build." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_UNITY_SOURCE_PRE_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file before the inclusion of this source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file before this file is included." + "Defaults to empty string." + ) + + define_property( + SOURCE PROPERTY "COTIRE_UNITY_SOURCE_POST_UNDEFS" + BRIEF_DOCS "Preprocessor undefs to place in the generated unity source file after the inclusion of this source file." + FULL_DOCS + "This may be set to a semicolon-separated list of preprocessor symbols." + "cotire will add corresponding #undef directives to the generated unit source file after this file is included." + "Defaults to empty string." + ) + + define_property( + SOURCE PROPERTY "COTIRE_START_NEW_UNITY_SOURCE" + BRIEF_DOCS "Start a new unity source file which includes this source file as the first one." + FULL_DOCS + "If this property is set to TRUE, cotire will complete the current unity file and start a new one." + "The new unity source file will include this source file as the first one." + "This property essentially works as a separator for unity source files." + "Defaults to FALSE." + ) + + define_property( + SOURCE PROPERTY "COTIRE_TARGET" + BRIEF_DOCS "Read-only property. Mark this source file as cotired for the given target." + FULL_DOCS + "cotire sets this property to the name of target, that the source file's build command has been altered for." + "Defaults to empty string." + ) + + message (STATUS "cotire ${COTIRE_CMAKE_MODULE_VERSION} loaded.") + +endif() diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..538791f --- /dev/null +++ b/config.lua @@ -0,0 +1,105 @@ +-- Combat settings +-- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" +worldType = "pvp" +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 = "127.0.0.1" +bindOnlyGlobalAddress = false +loginProtocolPort = 7171 +gameProtocolPort = 7172 +statusProtocolPort = 7171 +maxPlayers = 0 +motd = "Welcome to Nostalrius 4.5!" +onePlayerOnlinePerAccount = true +allowClones = false +serverName = "RealOTS" +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 = -1 + +-- 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 = "root" +mysqlPass = "" +mysqlDatabase = "nostalrius" +mysqlPort = 3306 +mysqlSock = "" + +-- Misc. +allowChangeOutfit = true +freePremium = false +kickIdlePlayerAfterMinutes = 15 +maxMessageBuffer = 4 +showMonsterLoot = 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 = 1 +rateLoot = 1 +rateMagic = 1 +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 = "" +ownerEmail = "" +url = "https://otland.net/" +location = "Sweden" diff --git a/data/XML/commands.xml b/data/XML/commands.xml new file mode 100644 index 0000000..a000e63 --- /dev/null +++ b/data/XML/commands.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/data/XML/groups.xml b/data/XML/groups.xml new file mode 100644 index 0000000..11923c7 --- /dev/null +++ b/data/XML/groups.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/data/XML/stages.xml b/data/XML/stages.xml new file mode 100644 index 0000000..6895898 --- /dev/null +++ b/data/XML/stages.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/XML/vocations.xml b/data/XML/vocations.xml new file mode 100644 index 0000000..b681928 --- /dev/null +++ b/data/XML/vocations.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/actions/actions.xml b/data/actions/actions.xml new file mode 100644 index 0000000..d8fe992 --- /dev/null +++ b/data/actions/actions.xml @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/actions/lib/actions.lua b/data/actions/lib/actions.lua new file mode 100644 index 0000000..1544460 --- /dev/null +++ b/data/actions/lib/actions.lua @@ -0,0 +1,20 @@ +function doDestroyItem(target) + if not target:isItem() then + return false + end + + local itemType = ItemType(target:getId()) + if not itemType:isDestroyable() then + return false + end + + if math.random(1,10) <= 3 then + target:transform(itemType:getDestroyTarget()) + target:decay() + target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) + else + target:getPosition():sendMagicEffect(CONST_ME_POFF) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/misc/baking.lua b/data/actions/scripts/misc/baking.lua new file mode 100644 index 0000000..11a3552 --- /dev/null +++ b/data/actions/scripts/misc/baking.lua @@ -0,0 +1,45 @@ +local ovens = { + 2535, 2537, 2539, 2541, 3510 +} + +local milestone = { + 1943, 1944, 1945, 1946 +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if item:getId() == 3603 then + if (target:getId() == 2524 or target:getId() == 2873) and target:getFluidType() == FLUID_WATER then + target:transform(target:getId(), FLUID_NONE) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3604, 1):decay() + else + Game.createItem(3604, 1, item:getPosition()):decay() + end + item:remove(1) + return true + end + elseif item:getId() == 3604 then + if table.contains(ovens, target:getId()) then + Game.createItem(3600, 1, target:getPosition()) + item:remove(1) + return true + end + elseif item:getId() == 3605 then + if table.contains(milestone, target:getId()) then + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3603, 1):decay() + else + Game.createItem(3603, 1, item:getPosition()):decay() + end + item:remove(1) + return true + end + end + return false +end diff --git a/data/actions/scripts/misc/birdcage.lua b/data/actions/scripts/misc/birdcage.lua new file mode 100644 index 0000000..276a7ce --- /dev/null +++ b/data/actions/scripts/misc/birdcage.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 1 and math.random(1, 100) <= 10 then + item:transform(2975, 0) + item:decay() + else + item:getPosition():sendMagicEffect(22) + end + return true +end diff --git a/data/actions/scripts/misc/blueberry_bush.lua b/data/actions/scripts/misc/blueberry_bush.lua new file mode 100644 index 0000000..4a40a50 --- /dev/null +++ b/data/actions/scripts/misc/blueberry_bush.lua @@ -0,0 +1,6 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:transform(3700, 1) + item:decay() + Game.createItem(3588, 3, fromPosition) + return true +end diff --git a/data/actions/scripts/misc/botanist_container.lua b/data/actions/scripts/misc/botanist_container.lua new file mode 100644 index 0000000..4eb510a --- /dev/null +++ b/data/actions/scripts/misc/botanist_container.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3874 and player:getStorageValue(305) == 1 then + item:transform(4868, 1) + target:getPosition():sendMagicEffect(10) + return true + elseif target:getId() == 3885 and player:getStorageValue(305) == 3 then + item:transform(4870, 1) + target:getPosition():sendMagicEffect(10) + return true + elseif target:getId() == 3878 and player:getStorageValue(305) == 5 then + item:transform(4869, 1) + target:getPosition():sendMagicEffect(10) + return true + end + return false +end diff --git a/data/actions/scripts/misc/butterfly_conservation_kit.lua b/data/actions/scripts/misc/butterfly_conservation_kit.lua new file mode 100644 index 0000000..83e96bc --- /dev/null +++ b/data/actions/scripts/misc/butterfly_conservation_kit.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4992 and player:getStorageValue(304) == 1 then + target:getPosition():sendMagicEffect(17) + item:transform(4865, 1) + item:decay() + target:remove() + elseif target:getId() == 4993 and player:getStorageValue(304) == 3 then + target:getPosition():sendMagicEffect(17) + item:transform(4866, 1) + item:decay() + target:remove() + elseif target:getId() == 4991 and player:getStorageValue(304) == 5 then + target:getPosition():sendMagicEffect(17) + item:transform(4864, 1) + item:decay() + target:remove() + elseif target:getId() == 5013 and player:getStorageValue(304) == 5 then + target:getPosition():sendMagicEffect(17) + item:transform(5089, 1) + item:decay() + target:remove() + end + return false +end diff --git a/data/actions/scripts/misc/chests.lua b/data/actions/scripts/misc/chests.lua new file mode 100644 index 0000000..d42b938 --- /dev/null +++ b/data/actions/scripts/misc/chests.lua @@ -0,0 +1,49 @@ +function onUse(player, item, fromPosition, target, toPosition) + local chestQuestNumber = item:getAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) + + if player:getStorageValue(chestQuestNumber) > 0 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The " .. item:getName() .. " is empty.") + return true + end + + local playerCapacity = player:getFreeCapacity() + + if item:getSize() <= 0 then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The chest is empty. This is a bug, report it to a gamemaster.") + return true + end + + local reward = item:getItem(0) + local stackable = reward:getType():isStackable() + local rewardName = reward:getName() + local rewardWeight = reward:getWeight() + + if stackable then + if reward:getCount() > 1 then + rewardName = reward:getCount() .. " " .. reward:getPluralName() + else + rewardName = reward:getName() + end + end + + if reward:getArticle():len() > 0 and reward:getCount() <= 1 then + rewardName = reward:getArticle() .. " " .. rewardName + end + + if rewardWeight > playerCapacity and not getPlayerFlagValue(player, layerFlag_HasInfiniteCapacity) then + local term = "it is" + if stackable and reward:getCount() > 1 then + term = "they are" + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, string.format("You have found %s. Weighing %d.%02d oz %s too heavy.", rewardName, rewardWeight / 100, rewardWeight % 100, term)) + return true + end + + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have found " .. rewardName .. ".") + player:addItemEx(reward:clone(), true) + if not getPlayerFlagValue(player, PlayerFlag_HasInfiniteCapacity) then + player:setStorageValue(chestQuestNumber, 1) + end + return true +end diff --git a/data/actions/scripts/misc/closed_trap.lua b/data/actions/scripts/misc/closed_trap.lua new file mode 100644 index 0000000..5f50e44 --- /dev/null +++ b/data/actions/scripts/misc/closed_trap.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if Tile(item:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then + item:getPosition():sendMagicEffect(3) + else + item:transform(3482, 1) + item:decay() + end + return true +end diff --git a/data/actions/scripts/misc/cornucopia.lua b/data/actions/scripts/misc/cornucopia.lua new file mode 100644 index 0000000..572952d --- /dev/null +++ b/data/actions/scripts/misc/cornucopia.lua @@ -0,0 +1,61 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 95 then + item:getPosition():sendMagicEffect(19) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + else + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + end + else + item:getPosition():sendMagicEffect(19) + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + parent:addItem(3592, 1) + else + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + Game.createItem(3592, 1, fromPosition) + end + item:transform(3592, 1) + item:decay() + end + return true +end diff --git a/data/actions/scripts/misc/crowbar.lua b/data/actions/scripts/misc/crowbar.lua new file mode 100644 index 0000000..7064de6 --- /dev/null +++ b/data/actions/scripts/misc/crowbar.lua @@ -0,0 +1,36 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4848 and player:getStorageValue(297) == 0 then + player:setStorageValue(297, 1) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 1) + target:decay() + return true + elseif target:getId() == 4848 and player:getStorageValue(297) == 1 then + player:setStorageValue(297, 2) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 1) + target:decay() + return true + elseif target:getId() == 4848 and player:getStorageValue(297) == 2 then + player:setStorageValue(297, 3) + target:getPosition():sendMagicEffect(1) + target:transform(4849, 3) + target:decay() + return true + elseif target:getId() == 1628 and toPosition.x == 32680 and toPosition.y == 32083 and toPosition.z == 09 then + Game.transformItemOnMap({x = 32680, y = 32083, z = 09}, 1628, 1630) + return true + elseif target:getId() == 3501 and toPosition.x == 32013 and toPosition.y == 31562 and toPosition.z == 04 and player:getStorageValue(228) == 1 then + Game.sendMagicEffect({x = 32013, y = 31562, z = 04}, 15) + player:setStorageValue(228, 2) + return true + elseif target:getId() == 3501 and toPosition.x == 32013 and toPosition.y == 31562 and toPosition.z == 04 then + Game.sendMagicEffect({x = 32013, y = 31562, z = 04}, 3) + return true + end + return doDestroyItem(target) +end diff --git a/data/actions/scripts/misc/doors.lua b/data/actions/scripts/misc/doors.lua new file mode 100644 index 0000000..e9b9cc6 --- /dev/null +++ b/data/actions/scripts/misc/doors.lua @@ -0,0 +1,175 @@ +local lockedDoors = { + 1628, 1631, 1650, 1653, 1668, 1671, 1682, 1691, 5006, 5007 +} + +local closedNormalDoors = { + [1629] = 1630, + [1632] = 1633, + [1638] = 1639, + [1640] = 1641, + [1651] = 1652, + [1654] = 1655, + [1656] = 1657, + [1658] = 1659, + [1669] = 1670, + [1672] = 1673, + [1683] = 1684, + [1685] = 1686, + [1692] = 1693, + [1694] = 1695, + [4912] = 4911, + [4913] = 4914, + [5082] = 5083, + [5084] = 5085, + [2177] = 2178, + [2179] = 2180, +} + +local openVerticalDoors = { + [1630] = 1629, + [1639] = 1638, + [1643] = 1642, + [1647] = 1646, + [1652] = 1651, + [1657] = 1656, + [1661] = 1660, + [1665] = 1664, + [1670] = 1669, + [1675] = 1674, + [1679] = 1678, + [1693] = 1692, + [1695] = 1694, + [1697] = 1696, + [1699] = 1698, + [4914] = 4913, + [5083] = 5082, + [2178] = 2177, +} + +local openHorizontalDoors = { + [1633] = 1632, + [1641] = 1640, + [1645] = 1644, + [1649] = 1648, + [1655] = 1654, + [1659] = 1658, + [1663] = 1662, + [1667] = 1666, + [1673] = 1672, + [1677] = 1676, + [1681] = 1680, + [1684] = 1683, + [1686] = 1685, + [1688] = 1687, + [1690] = 1689, + [4911] = 4912, + [5085] = 5084, + [2180] = 2179, +} + +local levelDoors = { + [1646] = 1647, + [1648] = 1649, + [1664] = 1665, + [1666] = 1667, + [1678] = 1679, + [1680] = 1681, + [1687] = 1688, + [1696] = 1697, +} + +local questDoors = { + [1642] = 1643, + [1644] = 1645, + [1660] = 1661, + [1662] = 1663, + [1674] = 1675, + [1676] = 1677, + [1689] = 1690, + [1698] = 1699, +} + +local passthrough = { + [2334] = 2335, + [2335] = 2334, + [2336] = 2337, + [2337] = 2336, + [2338] = 2339, + [2339] = 2338, + [2340] = 2341, + [2341] = 2340, +} + +function onUse(player, item, fromPosition, target, toPosition) + if table.contains(lockedDoors, item:getId()) then + player:sendTextMessage(MESSAGE_INFO_DESCR, "It is locked.") + return true + end + + local door = closedNormalDoors[item:getId()] + if door then + item:transform(door, 1) + item:decay() + return true + end + + door = openVerticalDoors[item:getId()] + if door then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(1, 0, 0), true) + end + item:transform(door, 1) + item:decay() + return true + end + + door = openHorizontalDoors[item:getId()] + if door then + local doorCreature = Tile(item:getPosition()):getTopCreature() + if doorCreature then + doorCreature:teleportTo(item:getPosition():moveRel(0, 1, 0), true) + end + item:transform(door, 1) + item:decay() + return true + end + + door = levelDoors[item:getId()] + if door then + if player:getLevel() < item:getAttribute(ITEM_ATTRIBUTE_DOORLEVEL) then + player:sendTextMessage(MESSAGE_INFO_DESCR, item:getType():getDescription() .. ".") + return true + end + + player:teleportTo(item:getPosition(), true) + item:transform(door, 1) + item:decay() + return true + end + + door = questDoors[item:getId()] + if door then + local questNumber = item:getAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER) + local questValue = item:getAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE) + if questNumber > 0 then + if player:getStorageValue(questNumber) ~= questValue then + player:sendTextMessage(MESSAGE_INFO_DESCR, "The door seems to be sealed against unwanted intruders.") + return true + end + end + + player:teleportTo(item:getPosition(), true) + item:transform(door, 1) + item:decay() + return true + end + + door = passthrough[item:getId()] + if door then + item:transform(door, 1) + item:decay() + return true + end + return true +end diff --git a/data/actions/scripts/misc/ectoplasm_container.lua b/data/actions/scripts/misc/ectoplasm_container.lua new file mode 100644 index 0000000..19838bb --- /dev/null +++ b/data/actions/scripts/misc/ectoplasm_container.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4094 then + item:transform(4853, 1) + item:decay() + target:getPosition():sendMagicEffect(12) + item:getPosition():sendMagicEffect(13) + return true + end + return false +end diff --git a/data/actions/scripts/misc/fishing_rod.lua b/data/actions/scripts/misc/fishing_rod.lua new file mode 100644 index 0000000..cb2692e --- /dev/null +++ b/data/actions/scripts/misc/fishing_rod.lua @@ -0,0 +1,39 @@ +local water = { + 4597, 4598, 4599, 4600, 4601, 4602, + 4609, 4610, 4611, 4612, 4613, 4614, + 4615, 4616, 4617, 4618, 4619, 4620, + 622 +} + +local fishableWater = { + 4597, 4598, 4599, 4600, 4601, 4602 +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if not table.contains(water, target:getId()) then + return false + end + + if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) then + if player:getItemCount(3492) >= 1 then + player:addSkillTries(SKILL_FISHING, 1) + if math.random(1, 100) <= math.min(math.max(10 + (player:getEffectiveSkillLevel(SKILL_FISHING) - 10) * 0.597, 10), 50) then + player:addItem(3578, 1) + + if target:getId() ~= 622 then + target:transform(4609, 1) + end + + target:decay() + player:removeItem(3492, 1) + end + end + end + + target:getPosition():sendMagicEffect(2) + return true +end diff --git a/data/actions/scripts/misc/fluids.lua b/data/actions/scripts/misc/fluids.lua new file mode 100644 index 0000000..4bb92b4 --- /dev/null +++ b/data/actions/scripts/misc/fluids.lua @@ -0,0 +1,102 @@ +local drunk = Condition(CONDITION_DRUNK) +drunk:setParameter(CONDITION_PARAM_TICKS, 60000) + +local poison = Condition(CONDITION_POISON) +poison:setParameter(CONDITION_PARAM_DELAYED, true) +poison:setParameter(CONDITION_PARAM_MINVALUE, -50) +poison:setParameter(CONDITION_PARAM_MAXVALUE, -120) +poison:setParameter(CONDITION_PARAM_STARTVALUE, -5) +poison:setParameter(CONDITION_PARAM_TICKINTERVAL, 5000) +poison:setParameter(CONDITION_PARAM_FORCEUPDATE, true) + +local messages = { + [FLUID_WATER] = "Gulp.", + [FLUID_WINE] = "Aah...", + [FLUID_BEER] = "Aah...", + [FLUID_MUD] = "Gulp.", + [FLUID_BLOOD] = "Gulp.", + [FLUID_SLIME] = "Urgh!", + [FLUID_OIL] = "Gulp.", + [FLUID_URINE] = "Urgh!", + [FLUID_MILK] = "Mmmh.", + [FLUID_MANAFLUID] = "Aaaah...", + [FLUID_LIFEFLUID] = "Aaaah...", + [FLUID_LEMONADE] = "Mmmh." +} + +function onUse(player, item, fromPosition, target, toPosition) + local targetItemType = ItemType(target:getId()) + if targetItemType and targetItemType:isFluidContainer() then + if target:getFluidType() == 0 and item:getFluidType() ~= 0 then + target:transform(target:getId(), item:getFluidType()) + item:transform(item:getId(), 0) + return true + elseif target:getFluidType() ~= 0 and item:getFluidType() == 0 then + target:transform(target:getId(), 0) + item:transform(item:getId(), target:getFluidType()) + return true + end + end + + if target:isCreature() then + if item:getFluidType() == FLUID_NONE then + player:sendCancelMessage("It is empty.") + else + local self = target == player + if self and item:getFluidType() == FLUID_BEER or item:getFluidType() == FLUID_WINE then + player:addCondition(drunk) + elseif self and item:getFluidType() == FLUID_SLIME then + player:addCondition(slime) + elseif item:getFluidType() == FLUID_MANAFLUID then + target:addMana(math.random(50, 100)) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + elseif item:getFluidType() == FLUID_LIFEFLUID then + target:addHealth(math.random(25, 50)) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + end + + if not self then + if item:getFluidType() ~= FLUID_MANAFLUID and item:getFluidType() ~= FLUID_LIFEFLUID then + if toPosition.x == CONTAINER_POSITION then + toPosition = player:getPosition() + end + Game.createItem(2886, item:getFluidType(), toPosition):decay() + return true + end + end + + local message = messages[item:getFluidType()] + if message then + target:say(message, TALKTYPE_MONSTER_SAY) + else + target:say("Gulp.", TALKTYPE_MONSTER_SAY) + end + item:transform(item:getId(), FLUID_NONE) + end + else + if toPosition.x == CONTAINER_POSITION then + toPosition = player:getPosition() + end + + local tile = Tile(toPosition) + if not tile then + return false + end + + if item:getFluidType() ~= FLUID_NONE and tile:hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) then + return false + end + + local fluidSource = targetItemType and targetItemType:getFluidSource() or FLUID_NONE + if fluidSource ~= FLUID_NONE then + item:transform(item:getId(), fluidSource) + elseif item:getFluidType() == FLUID_NONE then + player:sendTextMessage(MESSAGE_STATUS_SMALL, "It is empty.") + else + + Game.createItem(2886, item.type, toPosition):decay() + item:transform(item:getId(), 0) + end + end + return true +end diff --git a/data/actions/scripts/misc/food.lua b/data/actions/scripts/misc/food.lua new file mode 100644 index 0000000..4496952 --- /dev/null +++ b/data/actions/scripts/misc/food.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + local itemType = ItemType(item:getId()) + local condition = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + if condition and math.floor(condition:getTicks() / 1000 + (itemType:getNutrition() * 12)) >= 1200 then + player:sendTextMessage(MESSAGE_STATUS_SMALL, "You are full.") + else + player:feed(itemType:getNutrition() * 12) + item:remove(1) + end + + return true +end diff --git a/data/actions/scripts/misc/furniture_parcels.lua b/data/actions/scripts/misc/furniture_parcels.lua new file mode 100644 index 0000000..f0f84cd --- /dev/null +++ b/data/actions/scripts/misc/furniture_parcels.lua @@ -0,0 +1,61 @@ +local parcels = { + [2775] = 2374, + [2776] = 2378, + [2777] = 2358, + [2778] = 2382, + [2779] = 2366, + [2780] = 2418, + [2781] = 2422, + [2782] = 2319, + [2783] = 2316, + [2784] = 2315, + [2785] = 2314, + [2786] = 2346, + [2787] = 2349, + [2788] = 2351, + [2789] = 2433, + [2790] = 2441, + [2791] = 2449, + [2792] = 2524, + [2793] = 2523, + [2794] = 2483, + [2795] = 2465, + [2796] = 2976, + [2797] = 2979, + [2798] = 2934, + [2799] = 3485, + [2800] = 2998, + [2801] = 2445, + [2802] = 2025, + [2803] = 2029, + [2804] = 2030, + [2805] = 2904, + [2806] = 3510, + [2807] = 2959, + [2808] = 2963, + [2809] = 2426, + [2810] = 2352, + [2811] = 2982, + [2812] = 2986, + [5086] = 5046, + [5087] = 5055, + [5088] = 5056, +} + +function onUse(player, item, fromPosition, target, toPosition) + local parcel = parcels[item:getId()] + if not parcel then + return false + end + + if not item:getParent():isTile() then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + elseif not Tile(fromPosition):getHouse() then + item:getPosition():sendMagicEffect(CONST_ME_POFF) + else + item:transform(parcel) + item:getPosition():sendMagicEffect(CONST_ME_POFF) + end + + return true +end diff --git a/data/actions/scripts/misc/helmet_of_the_ancients.lua b/data/actions/scripts/misc/helmet_of_the_ancients.lua new file mode 100644 index 0000000..563119f --- /dev/null +++ b/data/actions/scripts/misc/helmet_of_the_ancients.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3030 then + item:getPosition():sendMagicEffect(14) + item:transform(3230, 1) + item:decay() + target:remove(1) + return true + end + return false +end diff --git a/data/actions/scripts/misc/ice_pick.lua b/data/actions/scripts/misc/ice_pick.lua new file mode 100644 index 0000000..81a0e3c --- /dev/null +++ b/data/actions/scripts/misc/ice_pick.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4994 and player:getStorageValue(306) == 1 and player:getStorageValue(307) == 0 then + local parent = item:getParent() + if parent:isContainer() or parent:isPlayer() then + parent:addItem(4837, 1) + else + Game.createItem(4837, 1, item:getPosition()) + end + target:getPosition():sendMagicEffect(2) + player:setStorageValue(307, 1) + return true + end + return false +end diff --git a/data/actions/scripts/misc/instruments.lua b/data/actions/scripts/misc/instruments.lua new file mode 100644 index 0000000..21ba16f --- /dev/null +++ b/data/actions/scripts/misc/instruments.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() >= 2948 and item:getId() <= 2950 or item:getId() >= 2952 and item:getId() <= 2958 or + item:getId() >= 2963 and item:getId() <= 2964 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + elseif (item:getId() >= 2959 and item:getId() <= 2962 or item:getId() == 2965) and math.random(1, 100) <= 50 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_GREEN) + elseif item:getId() >= 2959 and item:getId() <= 2962 or item:getId() == 2965 then + item:getPosition():sendMagicEffect(CONST_ME_SOUND_PURPLE) + elseif item:getId() == 3219 then + item:getPosition():sendMagicEffect(19) + end + + return true +end diff --git a/data/actions/scripts/misc/key.lua b/data/actions/scripts/misc/key.lua new file mode 100644 index 0000000..1e15475 --- /dev/null +++ b/data/actions/scripts/misc/key.lua @@ -0,0 +1,56 @@ +local closedDoors = { + [1628] = 1630, + [1629] = 1628, + [1631] = 1633, + [1632] = 1631, + [1650] = 1652, + [1651] = 1650, + [1653] = 1655, + [1654] = 1653, + [1668] = 1670, + [1669] = 1668, + [1671] = 1673, + [1672] = 1671, + [1682] = 1684, + [1683] = 1682, + [1691] = 1693, + [1692] = 1691, +} + +local openDoors = { + [1630] = 1628, + [1633] = 1631, + [1652] = 1650, + [1655] = 1653, + [1670] = 1668, + [1673] = 1671, + [1684] = 1682, + [1693] = 1691, +} + +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + local door = closedDoors[target:getId()] + if not door then + door = openDoors[target:getId()] + end + + if not door then + return false + end + + local keyNumber = item:getAttribute(ITEM_ATTRIBUTE_KEYNUMBER) + local keyHoleNumber = target:getAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER) + + if keyHoleNumber == 0 or keyNumber ~= keyHoleNumber then + player:sendCancelMessage("The key does not match.") + return true + end + + target:transform(door) + target:decay() + return true +end diff --git a/data/actions/scripts/misc/knife.lua b/data/actions/scripts/misc/knife.lua new file mode 100644 index 0000000..828b577 --- /dev/null +++ b/data/actions/scripts/misc/knife.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3594 then + target:transform(2977, 1) + target:decay() + return true + end + return false +end diff --git a/data/actions/scripts/misc/letter_bag.lua b/data/actions/scripts/misc/letter_bag.lua new file mode 100644 index 0000000..ecfa1a7 --- /dev/null +++ b/data/actions/scripts/misc/letter_bag.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 3221 and toPosition.x == 31948 and toPosition.y == 31711 and toPosition.z == 06 then + item:transform(2859, 1) + item:decay() + player:setStorageValue(244, 2) + Game.sendMagicEffect({x = 31948, y = 31711, z = 06}, 19) + return true + end + return false +end diff --git a/data/actions/scripts/misc/machete.lua b/data/actions/scripts/misc/machete.lua new file mode 100644 index 0000000..7e77d39 --- /dev/null +++ b/data/actions/scripts/misc/machete.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3696 then + target:transform(3695, 1) + target:decay() + return true + elseif target:getId() == 3702 then + target:transform(3701, 1) + target:decay() + return true + elseif target:getId() == 2130 then + target:remove() + return true + end + return false +end diff --git a/data/actions/scripts/misc/open_trap.lua b/data/actions/scripts/misc/open_trap.lua new file mode 100644 index 0000000..e7457b7 --- /dev/null +++ b/data/actions/scripts/misc/open_trap.lua @@ -0,0 +1,6 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:transform(3481, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + return true +end diff --git a/data/actions/scripts/misc/panda_teddy.lua b/data/actions/scripts/misc/panda_teddy.lua new file mode 100644 index 0000000..30b4acf --- /dev/null +++ b/data/actions/scripts/misc/panda_teddy.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:sendCancelMessage("Hug me ^^") + return true +end diff --git a/data/actions/scripts/misc/pick.lua b/data/actions/scripts/misc/pick.lua new file mode 100644 index 0000000..45a8467 --- /dev/null +++ b/data/actions/scripts/misc/pick.lua @@ -0,0 +1,34 @@ +function onUse(player, item, fromPosition, target, toPosition) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + if ground:getId() == 372 then + ground:transform(394, 1) + ground:decay() + return true + elseif target:getId() == 1772 and toPosition.x == 32648 and toPosition.y == 32134 and toPosition.z == 10 and math.random(1, 100) <= 40 then + Game.sendMagicEffect({x = 32648, y = 32134, z = 10}, 3) + Game.removeItemOnMap({x = 32648, y = 32134, z = 10}, 1772) + return true + elseif target:getId() == 1772 and toPosition.x == 32648 and toPosition.y == 32134 and toPosition.z == 10 then + Game.sendMagicEffect({x = 32648, y = 32134, z = 10}, 3) + doTargetCombatHealth(0, player, COMBAT_PHYSICALDAMAGE, -40, -40) + return true + elseif target:getId() == 1791 and toPosition.x == 32356 and toPosition.y == 32074 and toPosition.z == 10 and math.random(1, 100) <= 40 then + Game.sendMagicEffect({x = 32356, y = 32074, z = 10}, 3) + Game.removeItemOnMap({x = 32356, y = 32074, z = 10}, 1791) + return true + elseif target:getId() == 1791 and toPosition.x == 32356 and toPosition.y == 32074 and toPosition.z == 10 then + Game.sendMagicEffect({x = 32356, y = 32074, z = 10}, 3) + doTargetCombatHealth(0, player, COMBAT_PHYSICALDAMAGE, -50, -50) + return true + end + return false +end diff --git a/data/actions/scripts/misc/present.lua b/data/actions/scripts/misc/present.lua new file mode 100644 index 0000000..46f07d8 --- /dev/null +++ b/data/actions/scripts/misc/present.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(3) + item:remove() + return true +end diff --git a/data/actions/scripts/misc/pumpkin_head.lua b/data/actions/scripts/misc/pumpkin_head.lua new file mode 100644 index 0000000..047f37e --- /dev/null +++ b/data/actions/scripts/misc/pumpkin_head.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 2917 then + item:transform(2978, 1) + item:decay() + target:remove() + return true + end + return false +end diff --git a/data/actions/scripts/misc/rope.lua b/data/actions/scripts/misc/rope.lua new file mode 100644 index 0000000..9a17127 --- /dev/null +++ b/data/actions/scripts/misc/rope.lua @@ -0,0 +1,46 @@ +local ropeSpots = { + 386, 421 +} + +local holeSpots = { + 293, 294, 369, 370, 385, 394, 411, 412, + 421, 432, 433, 435, 482, 5081, 483, 594, + 595, 607, 609, 610, 615, 1066, 1067, 1080 +} + +function onUse(player, item, fromPosition, target, toPosition) + local tile = Tile(toPosition) + if not tile then + return false + end + + if not tile:getGround() then + return false + end + + if table.contains(ropeSpots, tile:getGround():getId()) then + player:teleportTo(target:getPosition():moveRel(0, 1, -1)) + return true + elseif table.contains(holeSpots, tile:getGround():getId()) or target:getId() == 435 then + local tile = Tile(target:getPosition():moveRel(0, 0, 1)) + if not tile then + return false + end + + local thing = tile:getTopCreature() + if not thing then + thing = tile:getTopVisibleThing() + end + + if thing:isCreature() then + thing:teleportTo(target:getPosition():moveRel(0, 1, 0), false) + return true + end + if thing:isItem() and thing:getType():isMovable() then + thing:moveTo(target:getPosition():moveRel(0, 1, 0)) + return true + end + return true + end + return false +end diff --git a/data/actions/scripts/misc/scythe.lua b/data/actions/scripts/misc/scythe.lua new file mode 100644 index 0000000..0fccda3 --- /dev/null +++ b/data/actions/scripts/misc/scythe.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 3652 then + player:sendCancelMessage(target:getType():getDescription() .. ".") + return true + elseif target:getId() == 3653 then + target:transform(3651, 1) + target:decay() + Game.createItem(3605, 1, target:getPosition()) + return true + end + return doDestroyItem(target) +end diff --git a/data/actions/scripts/misc/sheet_of_tracing_paper.lua b/data/actions/scripts/misc/sheet_of_tracing_paper.lua new file mode 100644 index 0000000..5e1ed0a --- /dev/null +++ b/data/actions/scripts/misc/sheet_of_tracing_paper.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 2199 and toPosition.x == 32754 and toPosition.y == 32559 and toPosition.z == 09 and player:getStorageValue(315) == 1 then + item:transform(4843, 1) + item:decay() + player:setStorageValue(316, 1) + target:getPosition():sendMagicEffect(4) + return true + end + return false +end diff --git a/data/actions/scripts/misc/shovel.lua b/data/actions/scripts/misc/shovel.lua new file mode 100644 index 0000000..66a1f04 --- /dev/null +++ b/data/actions/scripts/misc/shovel.lua @@ -0,0 +1,57 @@ +function onUse(player, item, fromPosition, target, toPosition) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + local toTarget = target; + + local itemType = ItemType(target:getId()) + if itemType:isSplash() then + toTarget = ground + end + + if toTarget:getId() == 231 then + toTarget:getPosition():sendMagicEffect(3) + return true + elseif toTarget:getId() == 593 then + toTarget:transform(594, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + return true + elseif toTarget:getId() == 606 then + toTarget:transform(607, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + return true + elseif toTarget:getId() == 608 then + toTarget:transform(609, 1) + toTarget:decay() + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + elseif toTarget:getId() == 614 and math.random(1, 100) <= 50 then + toTarget:transform(615, 1) + toTarget:decay() + toTarget:getPosition():sendMagicEffect(3) + doRelocate(toTarget:getPosition(), toTarget:getPosition():moveRel(0,0,1)) + elseif toTarget:getId() == 614 then + toTarget:getPosition():sendMagicEffect(3) + elseif toTarget:getId() == 616 and math.random(1, 100) <= 95 then + toTarget:transform(617, 1) + toTarget:decay() + toTarget:getPosition():sendMagicEffect(3) + Game.createMonster("scarab", toTarget:getPosition()) + elseif toTarget:getId() == 616 then + toTarget:getPosition():sendMagicEffect(3) + Game.createItem(3042, 1, toTarget:getPosition()) + toTarget:transform(617, 1) + toTarget:decay() + elseif toTarget:getId() == 617 then + toTarget:getPosition():sendMagicEffect(3) + end + return false +end diff --git a/data/actions/scripts/misc/snake_destroyer.lua b/data/actions/scripts/misc/snake_destroyer.lua new file mode 100644 index 0000000..e95e797 --- /dev/null +++ b/data/actions/scripts/misc/snake_destroyer.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 4850 and player:getStorageValue(293) == 17 then + target:transform(4851, 1) + target:decay() + player:setStorageValue(299, 1) + item:remove() + target:getPosition():sendMagicEffect(7) + return true + end + return false +end diff --git a/data/actions/scripts/misc/snowheap.lua b/data/actions/scripts/misc/snowheap.lua new file mode 100644 index 0000000..50a07f3 --- /dev/null +++ b/data/actions/scripts/misc/snowheap.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + Game.createItem(2992, 1, fromPosition) + return true +end diff --git a/data/actions/scripts/misc/special_rights.lua b/data/actions/scripts/misc/special_rights.lua new file mode 100644 index 0000000..66551be --- /dev/null +++ b/data/actions/scripts/misc/special_rights.lua @@ -0,0 +1,37 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:hasFlag(PlayerFlag_SpecialMoveUse) then + if item:getId() == 372 then + item:transform(394, 1) + item:decay() + elseif item:getId() == 386 or item:getId() == 421 then + local relPos = item:getPosition():moveRel(0, 1, -1) + player:teleportTo(relPos) + elseif item:getId() == 593 then + item:transform(594, 1) + item:decay() + doRelocate(item:getPosition(),item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 606 or item:getId() == 608 then + item:transform(607, 1) + item:decay() + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 614 then + item:transform(615, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 0, 1)) + elseif item:getId() == 3653 then + item:transform(3651, 1) + item:decay() + Game.createItem(3605, 1, item:getPosition()) + elseif item:getId() == 3696 then + item:transform(3695, 1) + item:decay() + elseif item:getId() == 3702 then + item:transform(3701, 1) + item:decay() + end + else + return false + end + return true +end diff --git a/data/actions/scripts/misc/spectral_stone.lua b/data/actions/scripts/misc/spectral_stone.lua new file mode 100644 index 0000000..a765752 --- /dev/null +++ b/data/actions/scripts/misc/spectral_stone.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if target:getId() == 599 and toPosition.x == 32665 and toPosition.y == 32736 and toPosition.z == 06 and player:getStorageValue(320) == 5 then + player:setStorageValue(321,1) + target:getPosition():sendMagicEffect(13) + return true + elseif target:getId() == 599 and toPosition.x == 32497 and toPosition.y == 31622 and toPosition.z == 06 and player:getStorageValue(320) == 5 then + player:setStorageValue(322,1) + target:getPosition():sendMagicEffect(13) + return true + end + return false +end diff --git a/data/actions/scripts/misc/spellbook.lua b/data/actions/scripts/misc/spellbook.lua new file mode 100644 index 0000000..2445cba --- /dev/null +++ b/data/actions/scripts/misc/spellbook.lua @@ -0,0 +1,32 @@ +function onUse(player, item, fromPosition, target, toPosition, isHotkey) + local count = getPlayerInstantSpellCount(player) + local text = "" + local spells = {} + for i = 0, count - 1 do + local spell = getPlayerInstantSpellInfo(player, i) + if spell.level ~= 0 then + if spell.manapercent > 0 then + spell.mana = spell.manapercent .. "%" + end + spells[#spells + 1] = spell + end + end + + table.sort(spells, function(a, b) return a.level < b.level end) + + local prevLevel = -1 + for i, spell in ipairs(spells) do + local line = "" + if prevLevel ~= spell.level then + if i ~= 1 then + line = "\n" + end + line = line .. "Spells for Level " .. spell.level .. "\n" + prevLevel = spell.level + end + text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n" + end + + player:showTextDialog(item:getId(), text) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/misc/strange_lever.lua b/data/actions/scripts/misc/strange_lever.lua new file mode 100644 index 0000000..da457f5 --- /dev/null +++ b/data/actions/scripts/misc/strange_lever.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2566 then + item:transform(2567, 1) + item:decay() + elseif item:getId() == 2567 then + player:sendCancelMessage("It doesn't move.") + elseif item:getId() == 2569 then + item:transform(2570, 1) + item:decay() + elseif item:getId() == 2570 then + item:transform(2569, 1) + item:decay() + end + return true +end diff --git a/data/actions/scripts/misc/teleporters.lua b/data/actions/scripts/misc/teleporters.lua new file mode 100644 index 0000000..03a74b9 --- /dev/null +++ b/data/actions/scripts/misc/teleporters.lua @@ -0,0 +1,16 @@ +local downstairs = { + 435 +} + +local upstairs = { + 1948, 1968 +} + +function onUse(player, item, fromPosition, target, toPosition) + if table.contains(downstairs, item:getId()) then + player:teleportTo(item:getPosition():moveRel(0, 0, 1)) + elseif table.contains(upstairs, item:getId()) then + player:teleportTo(item:getPosition():moveRel(0, 1, -1)) + end + return true +end diff --git a/data/actions/scripts/misc/time.lua b/data/actions/scripts/misc/time.lua new file mode 100644 index 0000000..98c7fcb --- /dev/null +++ b/data/actions/scripts/misc/time.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:sendTextMessage(MESSAGE_INFO_DESCR, "The time is " .. getFormattedWorldTime() .. ".") + return true +end diff --git a/data/actions/scripts/misc/used_lamp.lua b/data/actions/scripts/misc/used_lamp.lua new file mode 100644 index 0000000..ca56d87 --- /dev/null +++ b/data/actions/scripts/misc/used_lamp.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if not target:isItem() then + return false + end + + if target:getId() == 2874 and target:getFluidType() == FLUID_OIL then + target:transform(target:getId(), FLUID_NONE) + item:transform(2914, 1) + item:decay() + return true + end + return false +end diff --git a/data/actions/scripts/misc/water_pipe.lua b/data/actions/scripts/misc/water_pipe.lua new file mode 100644 index 0000000..2dc6444 --- /dev/null +++ b/data/actions/scripts/misc/water_pipe.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if math.random(1, 100) <= 90 then + item:getPosition():sendMagicEffect(3) + return true + else + player:getPosition():sendMagicEffect(3) + end + return true +end diff --git a/data/actions/scripts/misc/weapons.lua b/data/actions/scripts/misc/weapons.lua new file mode 100644 index 0000000..749279a --- /dev/null +++ b/data/actions/scripts/misc/weapons.lua @@ -0,0 +1,3 @@ +function onUse(player, item, fromPosition, target, toPosition) + return doDestroyItem(target) +end diff --git a/data/actions/scripts/nostalrius/1.lua b/data/actions/scripts/nostalrius/1.lua new file mode 100644 index 0000000..f8f19bc --- /dev/null +++ b/data/actions/scripts/nostalrius/1.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32627, y = 31699, z = 10}, 1771) then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32627, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + doRelocate({x = 32628, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + doRelocate({x = 32629, y = 31699, z = 10},{x = 32626, y = 31699, z = 10}) + Game.transformItemOnMap({x = 32627, y = 31699, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32627, y = 31699, z = 10}) + Game.transformItemOnMap({x = 32628, y = 31699, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32629, y = 31699, z = 10}, 1771, 622) + Game.createItem(4786, 1, {x = 32629, y = 31699, z = 10}) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32628, y = 31699, z = 10}, 622) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32627, y = 31699, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32628, y = 31699, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32629, y = 31699, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32627, y = 31699, z = 10}, 4788) + Game.removeItemOnMap({x = 32629, y = 31699, z = 10}, 4786) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/10.lua b/data/actions/scripts/nostalrius/10.lua new file mode 100644 index 0000000..3f542d2 --- /dev/null +++ b/data/actions/scripts/nostalrius/10.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32792, y = 31581, z = 07},1282) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32792, y = 31581, z = 07}, 1282) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32792, y = 31581, z = 07}, 1282) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32792, y = 31581, z = 07},{x = 32792, y = 31582, z = 07}) + Game.createItem(1282, 1, {x = 32792, y = 31581, z = 07}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/11.lua b/data/actions/scripts/nostalrius/11.lua new file mode 100644 index 0000000..4608503 --- /dev/null +++ b/data/actions/scripts/nostalrius/11.lua @@ -0,0 +1,32 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32685, y = 32084, z = 09}, 1771) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32687, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32686, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32685, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + doRelocate({x = 32684, y = 32084, z = 09},{x = 32683, y = 32084, z = 09}) + Game.transformItemOnMap({x = 32687, y = 32084, z = 09}, 1771, 727) + Game.createItem(4798, 1, {x = 32687, y = 32084, z = 09}) + Game.transformItemOnMap({x = 32686, y = 32084, z = 09}, 1771, 727) + Game.transformItemOnMap({x = 32685, y = 32084, z = 09}, 1771, 727) + Game.transformItemOnMap({x = 32684, y = 32084, z = 09}, 1771, 727) + Game.createItem(4800, 1, {x = 32684, y = 32084, z = 09}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2772 and Game.isItemThere({x = 32685, y = 32084, z = 09},727) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32684, y = 32084, z = 09}, 4800) + Game.transformItemOnMap({x = 32684, y = 32084, z = 09}, 727, 1771) + Game.transformItemOnMap({x = 32685, y = 32084, z = 09}, 727, 1771) + Game.removeItemOnMap({x = 32687, y = 32084, z = 09}, 4798) + Game.transformItemOnMap({x = 32687, y = 32084, z = 09}, 727, 1771) + Game.transformItemOnMap({x = 32686, y = 32084, z = 09}, 727, 1771) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/12.lua b/data/actions/scripts/nostalrius/12.lua new file mode 100644 index 0000000..5505cb4 --- /dev/null +++ b/data/actions/scripts/nostalrius/12.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32673, y = 32085, z = 08},430) and Game.isItemThere ({x = 32669, y = 32089, z = 08},430) and Game.isItemThere ({x = 32673, y = 32093, z = 08},430) and Game.isItemThere ({x = 32677, y = 32089, z = 08},430) and Game.isItemThere ({x = 32673, y = 32083, z = 08},3349) and Game.isItemThere ({x = 32667, y = 32089, z = 08},3585) and Game.isItemThere ({x = 32673, y = 32094, z = 08},3264) and Game.isItemThere ({x = 32679, y = 32089, z = 08},3059) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32673, y = 32083, z = 08}, 3349) + Game.removeItemOnMap({x = 32667, y = 32089, z = 08}, 3585) + Game.removeItemOnMap({x = 32673, y = 32094, z = 08}, 3264) + Game.removeItemOnMap({x = 32679, y = 32089, z = 08}, 3059) + Game.sendMagicEffect({x = 32673, y = 32083, z = 08}, 11) + Game.sendMagicEffect({x = 32667, y = 32089, z = 08}, 11) + Game.sendMagicEffect({x = 32673, y = 32094, z = 08}, 11) + Game.sendMagicEffect({x = 32679, y = 32089, z = 08}, 11) + doRelocate({x = 32673, y = 32093, z = 08},{x = 32671, y = 32069, z = 08}) + doRelocate({x = 32669, y = 32089, z = 08},{x = 32672, y = 32069, z = 08}) + doRelocate({x = 32673, y = 32085, z = 08},{x = 32671, y = 32070, z = 08}) + doRelocate({x = 32677, y = 32089, z = 08},{x = 32672, y = 32070, z = 08}) + Game.sendMagicEffect({x = 32671, y = 32069, z = 08}, 11) + Game.sendMagicEffect({x = 32672, y = 32069, z = 08}, 11) + Game.sendMagicEffect({x = 32671, y = 32070, z = 08}, 11) + Game.sendMagicEffect({x = 32672, y = 32070, z = 08}, 11) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/13.lua b/data/actions/scripts/nostalrius/13.lua new file mode 100644 index 0000000..e314472 --- /dev/null +++ b/data/actions/scripts/nostalrius/13.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32568, y = 32078, z = 12},2185) and Game.isItemThere ({x = 32569, y = 32078, z = 12},2185) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32568, y = 32078, z = 12}, 2185) + Game.removeItemOnMap({x = 32569, y = 32078, z = 12}, 2185) + elseif item:getId() == 2773 and Game.isItemThere({x = 32568, y = 32078, z = 12},2185) and Game.isItemThere ({x = 32569, y = 32078, z = 12}, 2185) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.createItem(2185, 1, {x = 32568, y = 32078, z = 12}) + Game.createItem(2185, 1, {x = 32569, y = 32078, z = 12}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/14.lua b/data/actions/scripts/nostalrius/14.lua new file mode 100644 index 0000000..74d5fa7 --- /dev/null +++ b/data/actions/scripts/nostalrius/14.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33314, y = 31592, z = 15}, 1842) + doRelocate({x = 33316, y = 31591, z = 15},{x = 33317, y = 31591, z = 15}) + Game.createItem(1949, 1, {x = 33316, y = 31591, z = 15}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33314, y = 31592, z = 15},{x = 33315, y = 31592, z = 15}) + Game.createItem(1842, 1, {x = 33314, y = 31592, z = 15}) + Game.removeItemOnMap({x = 33316, y = 31591, z = 15}, 1949) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/15.lua b/data/actions/scripts/nostalrius/15.lua new file mode 100644 index 0000000..8ec29c7 --- /dev/null +++ b/data/actions/scripts/nostalrius/15.lua @@ -0,0 +1,31 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33295, y = 31677, z = 15},1791) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33295, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33296, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33297, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33298, y = 31677, z = 15}, 1791) + Game.removeItemOnMap({x = 33299, y = 31677, z = 15}, 1791) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 33295, y = 31677, z = 15}, 1791) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33295, y = 31677, z = 15},{x = 33295, y = 31678, z = 15}) + doRelocate({x = 33296, y = 31677, z = 15},{x = 33296, y = 31678, z = 15}) + doRelocate({x = 33297, y = 31677, z = 15},{x = 33297, y = 31678, z = 15}) + doRelocate({x = 33298, y = 31677, z = 15},{x = 33298, y = 31678, z = 15}) + doRelocate({x = 33299, y = 31677, z = 15},{x = 33299, y = 31678, z = 15}) + Game.createItem(1791, 1, {x = 33295, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33296, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33297, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33298, y = 31677, z = 15}) + Game.createItem(1791, 1, {x = 33299, y = 31677, z = 15}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/16.lua b/data/actions/scripts/nostalrius/16.lua new file mode 100644 index 0000000..163597a --- /dev/null +++ b/data/actions/scripts/nostalrius/16.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33171, y = 31897, z = 08}, 1772) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33171, y = 31897, z = 08},{x = 33171, y = 31898, z = 08}) + Game.createItem(1772, 1, {x = 33171, y = 31897, z = 08}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/17.lua b/data/actions/scripts/nostalrius/17.lua new file mode 100644 index 0000000..4b534a3 --- /dev/null +++ b/data/actions/scripts/nostalrius/17.lua @@ -0,0 +1,31 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 33222, y = 31671, z = 13},430) and Game.isItemThere ({x = 33223, y = 31671, z = 13},430) and Game.isItemThere ({x = 33224, y = 31671, z = 13},430) and Game.isItemThere ({x = 33225, y = 31671, z = 13},430) and Game.isItemThere ({x = 33220, y = 31659, z = 13},1772) then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 33220, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33221, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33222, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33223, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33224, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33219, y = 31659, z = 13}, 1772) + Game.removeItemOnMap({x = 33219, y = 31657, z = 13}, 1772) + Game.removeItemOnMap({x = 33221, y = 31657, z = 13}, 1772) + Game.removeItemOnMap({x = 33220, y = 31661, z = 13}, 1772) + Game.removeItemOnMap({x = 33222, y = 31661, z = 13}, 1772) + Game.createMonster("Demon", {x = 33224, y = 31659, z = 13}) + Game.createMonster("Demon", {x = 33223, y = 31659, z = 13}) + Game.createMonster("Demon", {x = 33219, y = 31657, z = 13}) + Game.createMonster("Demon", {x = 33221, y = 31657, z = 13}) + Game.createMonster("Demon", {x = 33220, y = 31661, z = 13}) + Game.createMonster("Demon", {x = 33222, y = 31661, z = 13}) + doRelocate({x = 33222, y = 31671, z = 13},{x = 33219, y = 31659, z = 13}) + doRelocate({x = 33223, y = 31671, z = 13},{x = 33220, y = 31659, z = 13}) + doRelocate({x = 33224, y = 31671, z = 13},{x = 33221, y = 31659, z = 13}) + doRelocate({x = 33225, y = 31671, z = 13},{x = 33222, y = 31659, z = 13}) + Game.sendMagicEffect({x = 33219, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33220, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33221, y = 31659, z = 13}, 11) + Game.sendMagicEffect({x = 33222, y = 31659, z = 13}, 11) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/18.lua b/data/actions/scripts/nostalrius/18.lua new file mode 100644 index 0000000..6a66414 --- /dev/null +++ b/data/actions/scripts/nostalrius/18.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32483, y = 31633, z = 09}, 385) then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32483, y = 31633, z = 09}, 355, 385) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/19.lua b/data/actions/scripts/nostalrius/19.lua new file mode 100644 index 0000000..c339c4f --- /dev/null +++ b/data/actions/scripts/nostalrius/19.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:remove() + Game.createItem(2126, 1, {x = 32487, y = 31628, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31627, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31627, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31628, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31629, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31630, z = 13}) + Game.createItem(2126, 1, {x = 32486, y = 31626, z = 13}) + Game.createItem(2126, 1, {x = 32487, y = 31626, z = 13}) + Game.createItem(2126, 1, {x = 32488, y = 31626, z = 13}) + Game.sendMagicEffect({x = 32488, y = 31628, z = 13}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/2.lua b/data/actions/scripts/nostalrius/2.lua new file mode 100644 index 0000000..d58a377 --- /dev/null +++ b/data/actions/scripts/nostalrius/2.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if Game.isItemThere({x = 33211, y = 32698, z = 13}, 1306) then + Game.removeItemOnMap({x = 33211, y = 32698, z = 13}, 1306) + else + doRelocate({x = 33211, y = 32698, z = 13}, {x = 33211, y = 32697, z = 13}) + Game.createItem(1306, 1, {x = 33211, y = 32698, z = 13}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/20.lua b/data/actions/scripts/nostalrius/20.lua new file mode 100644 index 0000000..bc36d18 --- /dev/null +++ b/data/actions/scripts/nostalrius/20.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32259, y = 31891, z = 10},2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32259, y = 31891, z = 10}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32259, y = 31891, z = 10}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32259, y = 31891, z = 10},{x = 32259, y = 31892, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31891, z = 10}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/21.lua b/data/actions/scripts/nostalrius/21.lua new file mode 100644 index 0000000..f1fbfbd --- /dev/null +++ b/data/actions/scripts/nostalrius/21.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32313, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32313, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/22.lua b/data/actions/scripts/nostalrius/22.lua new file mode 100644 index 0000000..9d20100 --- /dev/null +++ b/data/actions/scripts/nostalrius/22.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32313, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32313, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32313, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/23.lua b/data/actions/scripts/nostalrius/23.lua new file mode 100644 index 0000000..a224a39 --- /dev/null +++ b/data/actions/scripts/nostalrius/23.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32311, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32311, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/24.lua b/data/actions/scripts/nostalrius/24.lua new file mode 100644 index 0000000..2d6ca6d --- /dev/null +++ b/data/actions/scripts/nostalrius/24.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32311, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32311, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32311, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/25.lua b/data/actions/scripts/nostalrius/25.lua new file mode 100644 index 0000000..5c9e50c --- /dev/null +++ b/data/actions/scripts/nostalrius/25.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32309, y = 31976, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31976, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32309, y = 31976, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31976, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/26.lua b/data/actions/scripts/nostalrius/26.lua new file mode 100644 index 0000000..e60621c --- /dev/null +++ b/data/actions/scripts/nostalrius/26.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32309, y = 31975, z = 13}, 1998) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31975, z = 13}, 1998, 1996) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32309, y = 31975, z = 13}, 1996) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32309, y = 31975, z = 13}, 1996, 1998) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/27.lua b/data/actions/scripts/nostalrius/27.lua new file mode 100644 index 0000000..d9bad72 --- /dev/null +++ b/data/actions/scripts/nostalrius/27.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32266, y = 31860, z = 11},2129) then + Game.removeItemOnMap({x = 32266, y = 31860, z = 11}, 2129) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 410, 411) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32266, y = 31860, z = 11}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/28.lua b/data/actions/scripts/nostalrius/28.lua new file mode 100644 index 0000000..1c16eab --- /dev/null +++ b/data/actions/scripts/nostalrius/28.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32259, y = 31890, z = 10},2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32259, y = 31890, z = 10}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32259, y = 31890, z = 10}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32259, y = 31890, z = 10},{x = 32259, y = 31889, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31890, z = 10}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/29.lua b/data/actions/scripts/nostalrius/29.lua new file mode 100644 index 0000000..98054d3 --- /dev/null +++ b/data/actions/scripts/nostalrius/29.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31845, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/3.lua b/data/actions/scripts/nostalrius/3.lua new file mode 100644 index 0000000..66f7438 --- /dev/null +++ b/data/actions/scripts/nostalrius/3.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 33148, y = 32867, z = 09}, 2129) and Game.isItemThere ({x = 33149, y = 32867, z = 09}, 2129) and Game.isItemThere ({x = 33148, y = 32868, z = 09}, 2129) and Game.isItemThere ({x = 33149, y = 32868, z = 09}, 2129) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 33148, y = 32867, z = 09}, 2129) + Game.removeItemOnMap({x = 33149, y = 32867, z = 09}, 2129) + Game.removeItemOnMap({x = 33148, y = 32868, z = 09}, 2129) + Game.removeItemOnMap({x = 33149, y = 32868, z = 09}, 2129) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 33148, y = 32867, z = 09}, 2129) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 33148, y = 32867, z = 09}, {x = 33148, y = 32869, z = 09}) + doRelocate({x = 33149, y = 32867, z = 09}, {x = 33149, y = 32869, z = 09}) + doRelocate({x = 33148, y = 32868, z = 09}, {x = 33148, y = 32869, z = 09}) + doRelocate({x = 33149, y = 32868, z = 09},{x = 33149, y = 32869, z = 09}) + Game.createItem(2129, 1, {x = 33148, y = 32867, z = 09}) + Game.createItem(2129, 1, {x = 33149, y = 32867, z = 09}) + Game.createItem(2129, 1, {x = 33148, y = 32868, z = 09}) + Game.createItem(2129, 1, {x = 33149, y = 32868, z = 09}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/30.lua b/data/actions/scripts/nostalrius/30.lua new file mode 100644 index 0000000..5667f38 --- /dev/null +++ b/data/actions/scripts/nostalrius/30.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31843, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31843, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/31.lua b/data/actions/scripts/nostalrius/31.lua new file mode 100644 index 0000000..0a71004 --- /dev/null +++ b/data/actions/scripts/nostalrius/31.lua @@ -0,0 +1,21 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31842, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31846, z = 14}, 12) + Game.transformItemOnMap({x = 32214, y = 31850, z = 15}, 2114, 2113) + Game.transformItemOnMap({x = 32215, y = 31850, z = 15}, 2114, 2113) + Game.transformItemOnMap({x = 32216, y = 31850, z = 15}, 2114, 2113) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/32.lua b/data/actions/scripts/nostalrius/32.lua new file mode 100644 index 0000000..4cd245a --- /dev/null +++ b/data/actions/scripts/nostalrius/32.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and Game.isItemThere({x = 32220, y = 31844, z = 15}, 2772) and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31844, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/33.lua b/data/actions/scripts/nostalrius/33.lua new file mode 100644 index 0000000..6053a06 --- /dev/null +++ b/data/actions/scripts/nostalrius/33.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 and player:getStorageValue(7) ~= 1 then + item:transform(2772, 1) + item:decay() + item:getPosition():sendMagicEffect(13) + Game.sendMagicEffect({x = 32217, y = 31843, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31842, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31841, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32218, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32219, y = 31845, z = 14}, 12) + Game.sendMagicEffect({x = 32220, y = 31845, z = 14}, 12) + elseif item:getId() == 2773 then + item:getPosition():sendMagicEffect(12) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -170, -170) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/34.lua b/data/actions/scripts/nostalrius/34.lua new file mode 100644 index 0000000..07c023c --- /dev/null +++ b/data/actions/scripts/nostalrius/34.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + elseif item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + elseif item:getId() == 2773 then + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + item:transform(2772, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/35.lua b/data/actions/scripts/nostalrius/35.lua new file mode 100644 index 0000000..7b97813 --- /dev/null +++ b/data/actions/scripts/nostalrius/35.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32604, y = 31905, z = 03}, 1789) + Game.removeItemOnMap({x = 32605, y = 31905, z = 03}, 1790) + Game.removeItemOnMap({x = 32604, y = 31904, z = 03}, 1787) + Game.removeItemOnMap({x = 32605, y = 31904, z = 03}, 1788) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32604, y = 31904, z = 03},{x = 32604, y = 31906, z = 03}) + doRelocate({x = 32604, y = 31905, z = 03},{x = 32604, y = 31906, z = 03}) + doRelocate({x = 32605, y = 31904, z = 03},{x = 32605, y = 31906, z = 03}) + doRelocate({x = 32605, y = 31905, z = 03},{x = 32605, y = 31906, z = 03}) + Game.createItem(1787, 1, {x = 32604, y = 31904, z = 03}) + Game.createItem(1789, 1, {x = 32604, y = 31905, z = 03}) + Game.createItem(1788, 1, {x = 32605, y = 31904, z = 03}) + Game.createItem(1790, 1, {x = 32605, y = 31905, z = 03}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/36.lua b/data/actions/scripts/nostalrius/36.lua new file mode 100644 index 0000000..44259c4 --- /dev/null +++ b/data/actions/scripts/nostalrius/36.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.transformItemOnMap({x = 32605, y = 31902, z = 04}, 436, 432) + item:transform(2773, 1) + item:decay() + doRelocate({x = 32605, y = 31902, z = 04},{x = 32605, y = 31902, z = 05}) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32605, y = 31902, z = 04}, 432, 436) + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/37.lua b/data/actions/scripts/nostalrius/37.lua new file mode 100644 index 0000000..0507b76 --- /dev/null +++ b/data/actions/scripts/nostalrius/37.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + doRelocate({x = 32636, y = 31881, z = 07},{x = 32636, y = 31881, z = 02}) + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32636, y = 31881, z = 02},{x = 32636, y = 31881, z = 07}) + Game.sendMagicEffect({x = 32636, y = 31881, z = 02}, 3) + Game.sendMagicEffect({x = 32636, y = 31881, z = 07}, 3) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/38.lua b/data/actions/scripts/nostalrius/38.lua new file mode 100644 index 0000000..0568787 --- /dev/null +++ b/data/actions/scripts/nostalrius/38.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32566, y = 32119, z = 07}, 1270) + Game.transformItemOnMap({x = 32566, y = 32118, z = 07}, 1270, 1274) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32566, y = 32119, z = 07},{x = 32567, y = 32119, z = 07}) + Game.createItem(1270, 1, {x = 32566, y = 32119, z = 07}) + Game.transformItemOnMap({x = 32566, y = 32118, z = 07}, 1274, 1270) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/39.lua b/data/actions/scripts/nostalrius/39.lua new file mode 100644 index 0000000..dbe8941 --- /dev/null +++ b/data/actions/scripts/nostalrius/39.lua @@ -0,0 +1,26 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32426, y = 32202, z = 14},{x = 32426, y = 32200, z = 14}) + doRelocate({x = 32426, y = 32201, z = 14},{x = 32426, y = 32200, z = 14}) + doRelocate({x = 32427, y = 32202, z = 14},{x = 32427, y = 32200, z = 14}) + doRelocate({x = 32427, y = 32201, z = 14},{x = 32427, y = 32200, z = 14}) + Game.removeItemOnMap({x = 32426, y = 32202, z = 14}, 1771) + Game.removeItemOnMap({x = 32426, y = 32201, z = 14}, 1771) + Game.removeItemOnMap({x = 32427, y = 32202, z = 14}, 1771) + Game.removeItemOnMap({x = 32427, y = 32201, z = 14}, 1771) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.createTile({x = 32426, y = 32201, z = 14}, true) + Game.createTile({x = 32427, y = 32201, z = 14}, true) + Game.createTile({x = 32426, y = 32202, z = 14}, true) + Game.createTile({x = 32427, y = 32202, z = 14}, true) + Game.createItem(1771, 1, {x = 32426, y = 32201, z = 14}) + Game.createItem(1771, 1, {x = 32427, y = 32201, z = 14}) + Game.createItem(1771, 1, {x = 32426, y = 32202, z = 14}) + Game.createItem(1771, 1, {x = 32427, y = 32202, z = 14}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/4.lua b/data/actions/scripts/nostalrius/4.lua new file mode 100644 index 0000000..9ca8929 --- /dev/null +++ b/data/actions/scripts/nostalrius/4.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/40.lua b/data/actions/scripts/nostalrius/40.lua new file mode 100644 index 0000000..47ffcdb --- /dev/null +++ b/data/actions/scripts/nostalrius/40.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.transformItemOnMap({x = 32313, y = 31928, z = 08}, 2772, 2773) + Game.sendMagicEffect({x = 32314, y = 31928, z = 08}, 12) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32313, y = 31928, z = 08}, 2773, 2772) + Game.sendMagicEffect({x = 32314, y = 31928, z = 08}, 12) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/41.lua b/data/actions/scripts/nostalrius/41.lua new file mode 100644 index 0000000..e58ecc6 --- /dev/null +++ b/data/actions/scripts/nostalrius/41.lua @@ -0,0 +1,15 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:remove() + Game.removeItemOnMap({x = 32186, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32187, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32188, y = 31626, z = 08}, 2129) + Game.removeItemOnMap({x = 32189, y = 31626, z = 08}, 2129) + Game.sendMagicEffect({x = 32180, y = 31633, z = 08}, 3) + Game.sendMagicEffect({x = 32186, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32187, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32188, y = 31626, z = 08}, 3) + Game.sendMagicEffect({x = 32189, y = 31626, z = 08}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/42.lua b/data/actions/scripts/nostalrius/42.lua new file mode 100644 index 0000000..820f05b --- /dev/null +++ b/data/actions/scripts/nostalrius/42.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32915, y = 32076, z = 06},388) then + Game.removeItemOnMap({x = 32915, y = 32076, z = 06}, 388) + Game.removeItemOnMap({x = 32915, y = 32080, z = 06}, 388) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and not Game.isItemThere({x = 32915, y = 32076, z = 06}, 388) then + doRelocate({x = 32915, y = 32076, z = 06},{x = 32916, y = 32076, z = 06}) + doRelocate({x = 32915, y = 32080, z = 06},{x = 32916, y = 32080, z = 06}) + Game.createItem(388, 1, {x = 32915, y = 32076, z = 06}) + Game.createItem(388, 1, {x = 32915, y = 32080, z = 06}) + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/43.lua b/data/actions/scripts/nostalrius/43.lua new file mode 100644 index 0000000..8d26791 --- /dev/null +++ b/data/actions/scripts/nostalrius/43.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32780, y = 32231, z = 08}, 389) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32780, y = 32231, z = 08},{x = 32780, y = 32232, z = 08}) + Game.createItem(389, 1, {x = 32780, y = 32231, z = 08}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/44.lua b/data/actions/scripts/nostalrius/44.lua new file mode 100644 index 0000000..f945df3 --- /dev/null +++ b/data/actions/scripts/nostalrius/44.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.removeItemOnMap({x = 32649, y = 32923, z = 08}, 1822) + Game.transformItemOnMap({x = 32649, y = 32923, z = 08}, 351, 385) + Game.transformItemOnMap({x = 32652, y = 32922, z = 08}, 2772, 2773) + elseif item:getId() == 2773 then + Game.transformItemOnMap({x = 32649, y = 32923, z = 08}, 385, 351) + Game.createItem(1822, 1, {x = 32649, y = 32923, z = 08}) + Game.transformItemOnMap({x = 32652, y = 32922, z = 08}, 2773, 2772) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/45.lua b/data/actions/scripts/nostalrius/45.lua new file mode 100644 index 0000000..2a6c75a --- /dev/null +++ b/data/actions/scripts/nostalrius/45.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2774 and Game.isItemThere({x = 33151, y = 32866, z = 08},1345) then + Game.removeItemOnMap({x = 33151, y = 32866, z = 08}, 1345) + Game.sendMagicEffect({x = 33151, y = 32862, z = 07}, 14) + elseif item:getId() == 2774 then + Game.sendMagicEffect({x = 33151, y = 32862, z = 07}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/46.lua b/data/actions/scripts/nostalrius/46.lua new file mode 100644 index 0000000..b67df79 --- /dev/null +++ b/data/actions/scripts/nostalrius/46.lua @@ -0,0 +1,42 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32614, y = 32209, z = 10},2523) then + Game.removeItemOnMap({x = 32614, y = 32209, z = 10}, 2523) + Game.removeItemOnMap({x = 32614, y = 32208, z = 10}, 1270) + doRelocate({x = 32614, y = 32209, z = 10},{x = 32614, y = 32208, z = 10}) + Game.createItem(2523, 1, {x = 32614, y = 32208, z = 10}) + Game.createItem(1270, 1, {x = 32614, y = 32209, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32206, z = 10}, 1791) + Game.removeItemOnMap({x = 32614, y = 32205, z = 10}, 1270) + Game.createItem(1810, 1, {x = 32614, y = 32204, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32221, z = 10}, 1270) + Game.removeItemOnMap({x = 32615, y = 32223, z = 10}, 1946) + Game.createItem(1796, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32615, y = 32221, z = 10}) + Game.createItem(2124, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32613, y = 32220, z = 10}) + Game.sendMagicEffect({x = 32613, y = 32220, z = 10}, 9) + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32613, y = 32220, z = 10}, 3050) + Game.createItem(1823, 1, {x = 32614, y = 32205, z = 10}) + Game.createItem(1270, 1, {x = 32614, y = 32206, z = 10}) + Game.sendMagicEffect({x = 32615, y = 32224, z = 10}, 16) + Game.sendMagicEffect({x = 32614, y = 32224, z = 10}, 16) + elseif item:getId() == 2772 then + doRelocate({x = 32614, y = 32209, z = 10},{x = 32613, y = 32209, z = 10}) + Game.removeItemOnMap({x = 32614, y = 32221, z = 10}, 1270) + Game.removeItemOnMap({x = 32615, y = 32223, z = 10}, 1946) + Game.createItem(1796, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32615, y = 32221, z = 10}) + Game.createItem(2124, 1, {x = 32615, y = 32223, z = 10}) + Game.createItem(2123, 1, {x = 32613, y = 32220, z = 10}) + Game.sendMagicEffect({x = 32613, y = 32220, z = 10}, 9) + Game.createItem(1270, 1, {x = 32614, y = 32209, z = 10}) + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32613, y = 32220, z = 10}, 3050) + elseif item:getId() == 2773 then + Game.sendMagicEffect({x = 32616, y = 32222, z = 10}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/47.lua b/data/actions/scripts/nostalrius/47.lua new file mode 100644 index 0000000..8230d26 --- /dev/null +++ b/data/actions/scripts/nostalrius/47.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32623, y = 32189, z = 09},{x = 32623, y = 32190, z = 09}, true) + doRelocate({x = 32623, y = 32188, z = 09},{x = 32623, y = 32189, z = 09}, true) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32623, y = 32188, z = 09},{x = 32622, y = 32189, z = 09}, true) + doRelocate({x = 32623, y = 32189, z = 09},{x = 32623, y = 32188, z = 09}, true) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/48.lua b/data/actions/scripts/nostalrius/48.lua new file mode 100644 index 0000000..adc1df2 --- /dev/null +++ b/data/actions/scripts/nostalrius/48.lua @@ -0,0 +1,40 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32594, y = 32214, z = 09},3050) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32603, y = 32216, z = 09}, 1626) + Game.removeItemOnMap({x = 32604, y = 32216, z = 09}, 1627) + doRelocate({x = 32603, y = 32216, z = 09},{x = 32603, y = 32217, z = 09}) + doRelocate({x = 32604, y = 32216, z = 09},{x = 32604, y = 32217, z = 09}) + doRelocate({x = 32593, y = 32216, z = 09},{x = 32592, y = 32216, z = 09}) + doRelocate({x = 32594, y = 32216, z = 09},{x = 32592, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32606, y = 32216, z = 09}, 1271) + Game.removeItemOnMap({x = 32607, y = 32216, z = 09}, 1271) + Game.transformItemOnMap({x = 32601, y = 32216, z = 09}, 1271, 1626) + Game.transformItemOnMap({x = 32602, y = 32216, z = 09}, 1271, 1627) + Game.createItem(1271, 1, {x = 32594, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32593, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32603, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32604, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32594, y = 32214, z = 09}, 3050) + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 9) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 3) + elseif item:getId() == 2773 and Game.isItemThere({x = 32594, y = 32214, z = 09},3050) then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32593, y = 32216, z = 09}, 1271) + Game.removeItemOnMap({x = 32594, y = 32216, z = 09}, 1271) + Game.transformItemOnMap({x = 32601, y = 32216, z = 09}, 1626, 1271) + Game.transformItemOnMap({x = 32602, y = 32216, z = 09}, 1627, 1271) + Game.transformItemOnMap({x = 32603, y = 32216, z = 09}, 1271, 1626) + Game.transformItemOnMap({x = 32604, y = 32216, z = 09}, 1271, 1627) + Game.createItem(1271, 1, {x = 32606, y = 32216, z = 09}) + Game.createItem(1271, 1, {x = 32607, y = 32216, z = 09}) + Game.removeItemOnMap({x = 32594, y = 32214, z = 09}, 3050) + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 9) + elseif item:getId() == 2773 then + Game.sendMagicEffect({x = 32594, y = 32214, z = 09}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/49.lua b/data/actions/scripts/nostalrius/49.lua new file mode 100644 index 0000000..926a547 --- /dev/null +++ b/data/actions/scripts/nostalrius/49.lua @@ -0,0 +1,12 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.createItem(2471, 1, {x = 32479, y = 31901, z = 05}) + Game.createItem(2122, 1, {x = 32479, y = 31892, z = 03}) + elseif item:getId() == 2773 and not Game.isItemThere({x = 32479, y = 31892, z = 03}, 2122) then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/5.lua b/data/actions/scripts/nostalrius/5.lua new file mode 100644 index 0000000..ba9db1c --- /dev/null +++ b/data/actions/scripts/nostalrius/5.lua @@ -0,0 +1,25 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and math.random(1, 100) <= 70 then + item:transform(2773, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3242, 1, {x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + doTargetCombatHealth(0, player, COMBAT_FIREDAMAGE, -200, -200) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3595, 1, {x = 33117, y = item:getPosition().y, z = 14}) + player:setStorageValue(258, 1) + Game.sendMagicEffect({x = 33122, y = 32765, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32761, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32762, z = 14}, 15) + Game.sendMagicEffect({x = 33117, y = 32763, z = 14}, 15) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemsOnMap({x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + Game.createItem(3573, 1, {x = item:getPosition().x - 1, y = item:getPosition().y, z = 14}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/50.lua b/data/actions/scripts/nostalrius/50.lua new file mode 100644 index 0000000..c1f4725 --- /dev/null +++ b/data/actions/scripts/nostalrius/50.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/51.lua b/data/actions/scripts/nostalrius/51.lua new file mode 100644 index 0000000..406573f --- /dev/null +++ b/data/actions/scripts/nostalrius/51.lua @@ -0,0 +1,18 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32476, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32477, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32478, y = 31900, z = 06},2772) and Game.isItemThere ({x = 32479, y = 31900, z = 06},2772) and Game.isItemThere ({x = 32480, y = 31900, z = 06},2773) and Game.isItemThere ({x = 32481, y = 31900, z = 06}, 2772) then + item:transform(2773, 1) + item:decay() + Game.createItem(1948, 1, {x = 32476, y = 31904, z = 06}) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32479, y = 31905, z = 06}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32476, y = 31904, z = 06}, 1948) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32476, y = 31904, z = 06}, 1948) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/52.lua b/data/actions/scripts/nostalrius/52.lua new file mode 100644 index 0000000..5722a6c --- /dev/null +++ b/data/actions/scripts/nostalrius/52.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32476, y = 31900, z = 04},3593) and Game.isItemThere ({x = 32477, y = 31900, z = 04},3587) and Game.isItemThere ({x = 32478, y = 31900, z = 04},3590) and Game.isItemThere ({x = 32479, y = 31900, z = 04},3585) and Game.isItemThere ({x = 32480, y = 31900, z = 04},3592) and Game.isItemThere ({x = 32481, y = 31900, z = 04},3589) then + Game.createItem(1948, 1, {x = 32476, y = 31904, z = 04}) + Game.removeItemOnMap({x = 32476, y = 31900, z = 04}, 3593) + Game.removeItemOnMap({x = 32477, y = 31900, z = 04}, 3587) + Game.removeItemOnMap({x = 32478, y = 31900, z = 04}, 3590) + Game.removeItemOnMap({x = 32479, y = 31900, z = 04}, 3585) + Game.removeItemOnMap({x = 32480, y = 31900, z = 04}, 3592) + Game.removeItemOnMap({x = 32481, y = 31900, z = 04}, 3589) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32479, y = 31905, z = 04}, 3) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/53.lua b/data/actions/scripts/nostalrius/53.lua new file mode 100644 index 0000000..4688ca8 --- /dev/null +++ b/data/actions/scripts/nostalrius/53.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32478, y = 31903, z = 03},3537) and Game.isItemThere ({x = 32479, y = 31903, z = 03},3543) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32478, y = 31903, z = 03}, 3537) + Game.removeItemOnMap({x = 32479, y = 31903, z = 03}, 3543) + Game.createItem(1948, 1, {x = 32479, y = 31904, z = 03}) + elseif item:getId() == 2772 then + Game.sendMagicEffect({x = 32478, y = 31904, z = 03}, 3) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32479, y = 31904, z = 03}, 1948) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/54.lua b/data/actions/scripts/nostalrius/54.lua new file mode 100644 index 0000000..049778a --- /dev/null +++ b/data/actions/scripts/nostalrius/54.lua @@ -0,0 +1,17 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32177, y = 32148, z = 11}, 1630) then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32177, y = 32148, z = 11},{x = 32178, y = 32148, z = 11}) + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1630, 1628) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1629, 1628) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32177, y = 32148, z = 11}, 1628, 1630) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/55.lua b/data/actions/scripts/nostalrius/55.lua new file mode 100644 index 0000000..1a751b8 --- /dev/null +++ b/data/actions/scripts/nostalrius/55.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + Game.removeItemOnMap({x = 32145, y = 32101, z = 11}, 1791) + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32145, y = 32101, z = 11},{x = 32145, y = 32102, z = 11}) + Game.createItem(1791, 1, {x = 32145, y = 32101, z = 11}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/56.lua b/data/actions/scripts/nostalrius/56.lua new file mode 100644 index 0000000..1646edf --- /dev/null +++ b/data/actions/scripts/nostalrius/56.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32098, y = 32204, z = 08}, 2772, 2773) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 622, 1771) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 622, 1771) + Game.removeItemOnMap({x = 32100, y = 32205, z = 08}, 4788) + Game.removeItemOnMap({x = 32101, y = 32205, z = 08}, 4786) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32098, y = 32204, z = 08}, 2773, 2772) + doRelocate({x = 32100, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + doRelocate({x = 32101, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 1771, 622) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 1771, 622) + Game.createItem(4788, 1, {x = 32100, y = 32205, z = 08}) + Game.createItem(4786, 1, {x = 32101, y = 32205, z = 08}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/57.lua b/data/actions/scripts/nostalrius/57.lua new file mode 100644 index 0000000..cb8ef6d --- /dev/null +++ b/data/actions/scripts/nostalrius/57.lua @@ -0,0 +1,23 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32104, y = 32204, z = 08}, 2772, 2773) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 622, 1771) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 622, 1771) + Game.removeItemOnMap({x = 32100, y = 32205, z = 08}, 4788) + Game.removeItemOnMap({x = 32101, y = 32205, z = 08}, 4786) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32104, y = 32204, z = 08}, 2773, 2772) + doRelocate({x = 32100, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + doRelocate({x = 32101, y = 32205, z = 08},{x = 32102, y = 32205, z = 08}) + Game.transformItemOnMap({x = 32100, y = 32205, z = 08}, 1771, 622) + Game.transformItemOnMap({x = 32101, y = 32205, z = 08}, 1771, 622) + Game.createItem(4788, 1, {x = 32100, y = 32205, z = 08}) + Game.createItem(4786, 1, {x = 32101, y = 32205, z = 08}) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/58.lua b/data/actions/scripts/nostalrius/58.lua new file mode 100644 index 0000000..0f55f98 --- /dev/null +++ b/data/actions/scripts/nostalrius/58.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32095, y = 32173, z = 08}, 1271) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32095, y = 32173, z = 08},{x = 32095, y = 32174, z = 08}) + Game.createItem(1271, 1, {x = 32095, y = 32173, z = 08}) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/59.lua b/data/actions/scripts/nostalrius/59.lua new file mode 100644 index 0000000..a07ce61 --- /dev/null +++ b/data/actions/scripts/nostalrius/59.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32088, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32088, y = 32149, z = 10},{x = 32088, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32088, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/6.lua b/data/actions/scripts/nostalrius/6.lua new file mode 100644 index 0000000..cd35aab --- /dev/null +++ b/data/actions/scripts/nostalrius/6.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32891, y = 32590, z = 11},2567) and Game.isItemThere ({x = 32843, y = 32649, z = 11},2567) and Game.isItemThere ({x = 32808, y = 32613, z = 11},2567) and Game.isItemThere ({x = 32775, y = 32583, z = 11},2567) and Game.isItemThere ({x = 32756, y = 32494, z = 11},2567) and Game.isItemThere ({x = 32799, y = 32556, z = 11},2567) and Game.isItemThere ({x = 32864, y = 32556, z = 11},1563) then + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.removeItemOnMap({x = 32864, y = 32556, z = 11}, 1563) + elseif item:getId() == 2772 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + player:sendCancelMessage("The lever won't budge.") + elseif item:getId() == 2773 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32864, y = 32556, z = 11},{x = 32864, y = 32557, z = 11}) + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.createItem(1563, 1, {x = 32864, y = 32556, z = 11}) + elseif item:getId() == 2773 and Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/60.lua b/data/actions/scripts/nostalrius/60.lua new file mode 100644 index 0000000..03185a7 --- /dev/null +++ b/data/actions/scripts/nostalrius/60.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32090, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32090, y = 32149, z = 10},{x = 32090, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32090, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/61.lua b/data/actions/scripts/nostalrius/61.lua new file mode 100644 index 0000000..b09bfce --- /dev/null +++ b/data/actions/scripts/nostalrius/61.lua @@ -0,0 +1,14 @@ +function onUse(player, item, fromPosition, target, toPosition) + if tem:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32092, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32092, y = 32149, z = 10},{x = 32092, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32092, y = 32149, z = 10}) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/62.lua b/data/actions/scripts/nostalrius/62.lua new file mode 100644 index 0000000..6257d4b --- /dev/null +++ b/data/actions/scripts/nostalrius/62.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32090, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32092, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32094, y = 32148, z = 09},2772) and Game.isItemThere ({x = 32088, y = 32148, z = 09},2772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32094, y = 32149, z = 10}, 1282) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32094, y = 32149, z = 10},{x = 32094, y = 32150, z = 10}) + Game.createItem(1282, 1, {x = 32094, y = 32149, z = 10}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/63.lua b/data/actions/scripts/nostalrius/63.lua new file mode 100644 index 0000000..71da6b0 --- /dev/null +++ b/data/actions/scripts/nostalrius/63.lua @@ -0,0 +1,29 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32413, y = 32230, z = 10}, 2772, 2773) + doRelocate({x = 32411, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32410, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32411, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + doRelocate({x = 32410, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32410, y = 32231, z = 10}) + Game.createItem(4788, 1, {x = 32410, y = 32232, z = 10}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32413, y = 32230, z = 10}, 2773, 2772) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32410, y = 32231, z = 10}, 4788) + Game.removeItemOnMap({x = 32410, y = 32232, z = 10}, 4788) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/64.lua b/data/actions/scripts/nostalrius/64.lua new file mode 100644 index 0000000..38f9a51 --- /dev/null +++ b/data/actions/scripts/nostalrius/64.lua @@ -0,0 +1,28 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32414, y = 32252, z = 10}, 2772, 2773) + doRelocate({x = 32411, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32410, y = 32231, z = 10},{x = 32412, y = 32231, z = 10}) + doRelocate({x = 32411, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + doRelocate({x = 32410, y = 32232, z = 10},{x = 32412, y = 32232, z = 10}) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 1771, 622) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 1771, 622) + Game.createItem(4788, 1, {x = 32410, y = 32231, z = 10}) + Game.createItem(4788, 1, {x = 32410, y = 32232, z = 10}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32414, y = 32252, z = 10}, 2773, 2772) + Game.transformItemOnMap({x = 32411, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32411, y = 32232, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32231, z = 10}, 622, 1771) + Game.transformItemOnMap({x = 32410, y = 32232, z = 10}, 622, 1771) + Game.removeItemOnMap({x = 32410, y = 32231, z = 10}, 4788) + Game.removeItemOnMap({x = 32410, y = 32232, z = 10}, 4788) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/65.lua b/data/actions/scripts/nostalrius/65.lua new file mode 100644 index 0000000..74d5ad6 --- /dev/null +++ b/data/actions/scripts/nostalrius/65.lua @@ -0,0 +1,20 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32400, y = 32241, z = 06}, 432) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32241, z = 06}, 432, 408) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32400, y = 32241, z = 06}, 408) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32241, z = 06}, 408, 432) + doRelocate({x = 32400, y = 32241, z = 06},{x = 32400, y = 32241, z = 07}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/66.lua b/data/actions/scripts/nostalrius/66.lua new file mode 100644 index 0000000..716079d --- /dev/null +++ b/data/actions/scripts/nostalrius/66.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32400, y = 32239, z = 06}, 432) then + item:transform(2773, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32239, z = 06}, 432, 408) + elseif item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 and Game.isItemThere({x = 32400, y = 32239, z = 06}, 408) then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32400, y = 32239, z = 06}, 408, 432) + doRelocate({x = 32400, y = 32239, z = 06},{x = 32400, y = 32239, z = 07}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/67.lua b/data/actions/scripts/nostalrius/67.lua new file mode 100644 index 0000000..5753d80 --- /dev/null +++ b/data/actions/scripts/nostalrius/67.lua @@ -0,0 +1,13 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32225, y = 32276, z = 08},{x = 32225, y = 32276, z = 09}) + Game.transformItemOnMap({x = 32225, y = 32276, z = 08}, 351, 369) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32276, z = 08}, 369, 351) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/68.lua b/data/actions/scripts/nostalrius/68.lua new file mode 100644 index 0000000..4658b20 --- /dev/null +++ b/data/actions/scripts/nostalrius/68.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + doRelocate({x = 32225, y = 32276, z = 10},{x = 32225, y = 32275, z = 10}) + Game.createItem(1949, 1, {x = 32225, y = 32276, z = 10}) + doRelocate({x = 32233, y = 32276, z = 09},{x = 32232, y = 32276, z = 09}) + Game.createItem(1949, 1, {x = 32233, y = 32276, z = 09}) + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + Game.removeItemOnMap({x = 32233, y = 32276, z = 09}, 1949) + Game.removeItemOnMap({x = 32225, y = 32276, z = 10}, 1949) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/69.lua b/data/actions/scripts/nostalrius/69.lua new file mode 100644 index 0000000..bc67cb5 --- /dev/null +++ b/data/actions/scripts/nostalrius/69.lua @@ -0,0 +1,22 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32592, y = 32104, z = 14},{x = 32591, y = 32104, z = 14}) + doRelocate({x = 32592, y = 32105, z = 14},{x = 32591, y = 32105, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32104, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32105, z = 14}) + Game.createItem(1270, 1, {x = 32592, y = 32106, z = 14}) + Game.removeItemOnMap({x = 32593, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32594, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32595, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32596, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32597, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32598, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32599, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32600, y = 32103, z = 14}, 1271) + Game.removeItemOnMap({x = 32601, y = 32103, z = 14}, 1271) + end + + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/7.lua b/data/actions/scripts/nostalrius/7.lua new file mode 100644 index 0000000..0ed5ef2 --- /dev/null +++ b/data/actions/scripts/nostalrius/7.lua @@ -0,0 +1,19 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2773, 1) + item:decay() + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.removeItemOnMap({x = 32864, y = 32556, z = 11}, 1563) + elseif item:getId() == 2772 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + player:sendCancelMessage("The lever won't budge.") + elseif item:getId() == 2773 and not Game.isItemThere({x = 32864, y = 32556, z = 11}, 1563) then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32864, y = 32556, z = 11},{x = 32864, y = 32557, z = 11}) + Game.sendMagicEffect({x = 32864, y = 32556, z = 11}, 14) + Game.createItem(1563, 1, {x = 32864, y = 32556, z = 11}) + elseif item:getId() == 2773 then + player:sendCancelMessage("The lever won't budge.") + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/70.lua b/data/actions/scripts/nostalrius/70.lua new file mode 100644 index 0000000..36e2051 --- /dev/null +++ b/data/actions/scripts/nostalrius/70.lua @@ -0,0 +1,8 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(287) == 1 then + player:setStorageValue(287, 2) + Game.createItem(3233, 1, player:getPosition()) + Game.sendMagicEffect(item:getPosition(), 2) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/71.lua b/data/actions/scripts/nostalrius/71.lua new file mode 100644 index 0000000..5998dc2 --- /dev/null +++ b/data/actions/scripts/nostalrius/71.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getItemCount(3231) >= 1 and player:getStorageValue(288) == 1 then + Game.sendMagicEffect({x = 33094, y = 32524, z = 01}, 14) + player:removeItem(3231, 1) + Game.createItem(3243, 1, {x = 33095, y = 32524, z = 01}) + player:setStorageValue(288, 2) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/72.lua b/data/actions/scripts/nostalrius/72.lua new file mode 100644 index 0000000..21cba51 --- /dev/null +++ b/data/actions/scripts/nostalrius/72.lua @@ -0,0 +1,9 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getItemCount(3231) >= 1 and player:getStorageValue(283) == 1 then + Game.sendMagicEffect({x = 33048, y = 32630, z = 01}, 14) + player:removeItem(3231, 1) + Game.createItem(3243, 1, {x = 33048, y = 32631, z = 01}) + player:setStorageValue(283, 2) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/73.lua b/data/actions/scripts/nostalrius/73.lua new file mode 100644 index 0000000..f08af11 --- /dev/null +++ b/data/actions/scripts/nostalrius/73.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 0 then + item:getPosition():sendMagicEffect(23) + player:setStorageValue(259, 1) + else + item:getPosition():sendMagicEffect(23) + player:setStorageValue(259, 0) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/74.lua b/data/actions/scripts/nostalrius/74.lua new file mode 100644 index 0000000..62b23d5 --- /dev/null +++ b/data/actions/scripts/nostalrius/74.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259, 0) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/75.lua b/data/actions/scripts/nostalrius/75.lua new file mode 100644 index 0000000..e3a7a91 --- /dev/null +++ b/data/actions/scripts/nostalrius/75.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 4 then + item:getPosition():sendMagicEffect(24) + player:setStorageValue(259,5) + else + item:getPosition():sendMagicEffect(24) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/76.lua b/data/actions/scripts/nostalrius/76.lua new file mode 100644 index 0000000..76f7273 --- /dev/null +++ b/data/actions/scripts/nostalrius/76.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 2 then + item:getPosition():sendMagicEffect(25) + player:setStorageValue(259,3) + else + item:getPosition():sendMagicEffect(25) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/77.lua b/data/actions/scripts/nostalrius/77.lua new file mode 100644 index 0000000..53dd344 --- /dev/null +++ b/data/actions/scripts/nostalrius/77.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259,0) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/78.lua b/data/actions/scripts/nostalrius/78.lua new file mode 100644 index 0000000..61b58c9 --- /dev/null +++ b/data/actions/scripts/nostalrius/78.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 3 then + item:getPosition():sendMagicEffect(22) + player:setStorageValue(259,4) + else + item:getPosition():sendMagicEffect(22) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/79.lua b/data/actions/scripts/nostalrius/79.lua new file mode 100644 index 0000000..1fd0e8d --- /dev/null +++ b/data/actions/scripts/nostalrius/79.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if player:getStorageValue(259) == 1 then + item:getPosition():sendMagicEffect(20) + player:setStorageValue(259,2) + else + item:getPosition():sendMagicEffect(20) + player:setStorageValue(259,0) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/8.lua b/data/actions/scripts/nostalrius/8.lua new file mode 100644 index 0000000..32be86b --- /dev/null +++ b/data/actions/scripts/nostalrius/8.lua @@ -0,0 +1,10 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 then + item:transform(2773, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/80.lua b/data/actions/scripts/nostalrius/80.lua new file mode 100644 index 0000000..53dd344 --- /dev/null +++ b/data/actions/scripts/nostalrius/80.lua @@ -0,0 +1,5 @@ +function onUse(player, item, fromPosition, target, toPosition) + item:getPosition():sendMagicEffect(19) + player:setStorageValue(259,0) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/81.lua b/data/actions/scripts/nostalrius/81.lua new file mode 100644 index 0000000..c33b962 --- /dev/null +++ b/data/actions/scripts/nostalrius/81.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32354, y = 32131, z = 9}) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/82.lua b/data/actions/scripts/nostalrius/82.lua new file mode 100644 index 0000000..58bd8a5 --- /dev/null +++ b/data/actions/scripts/nostalrius/82.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32172, y = 32439, z = 8}) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/83.lua b/data/actions/scripts/nostalrius/83.lua new file mode 100644 index 0000000..80f1034 --- /dev/null +++ b/data/actions/scripts/nostalrius/83.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + player:teleportTo({x = 32508, y = 32176, z = 14}) + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/9.lua b/data/actions/scripts/nostalrius/9.lua new file mode 100644 index 0000000..b46224e --- /dev/null +++ b/data/actions/scripts/nostalrius/9.lua @@ -0,0 +1,16 @@ +function onUse(player, item, fromPosition, target, toPosition) + if item:getId() == 2772 and Game.isItemThere({x = 32790, y = 31594, z = 07},1772) then + item:transform(2773, 1) + item:decay() + Game.removeItemOnMap({x = 32790, y = 31594, z = 07}, 1772) + elseif item:getId() == 2773 and Game.isItemThere({x = 32790, y = 31594, z = 07}, 1772) then + item:transform(2772, 1) + item:decay() + elseif item:getId() == 2773 then + item:transform(2772, 1) + item:decay() + doRelocate({x = 32790, y = 31594, z = 07},{x = 32790, y = 31595, z = 07}) + Game.createItem(1772, 1, {x = 32790, y = 31594, z = 07}) + end + return true +end \ No newline at end of file diff --git a/data/actions/scripts/nostalrius/_.lua b/data/actions/scripts/nostalrius/_.lua new file mode 100644 index 0000000..9c7ebc5 --- /dev/null +++ b/data/actions/scripts/nostalrius/_.lua @@ -0,0 +1,4 @@ +function onUse(player, item, fromPosition, target, toPosition) + + return true +end \ No newline at end of file diff --git a/data/chatchannels/chatchannels.xml b/data/chatchannels/chatchannels.xml new file mode 100644 index 0000000..67ae8f5 --- /dev/null +++ b/data/chatchannels/chatchannels.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/chatchannels/scripts/englishchat.lua b/data/chatchannels/scripts/englishchat.lua new file mode 100644 index 0000000..04cf10c --- /dev/null +++ b/data/chatchannels/scripts/englishchat.lua @@ -0,0 +1,22 @@ +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/data/chatchannels/scripts/gamemaster.lua b/data/chatchannels/scripts/gamemaster.lua new file mode 100644 index 0000000..b3050cc --- /dev/null +++ b/data/chatchannels/scripts/gamemaster.lua @@ -0,0 +1,21 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER +end + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType == ACCOUNT_TYPE_GOD then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType ~= ACCOUNT_TYPE_GOD then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType ~= ACCOUNT_TYPE_GOD and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/data/chatchannels/scripts/help.lua b/data/chatchannels/scripts/help.lua new file mode 100644 index 0000000..54e7de6 --- /dev/null +++ b/data/chatchannels/scripts/help.lua @@ -0,0 +1,77 @@ +local CHANNEL_HELP = 7 + +local muted = Condition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT) +muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_HELP) +muted:setParameter(CONDITION_PARAM_TICKS, 3600000) + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType == ACCOUNT_TYPE_NORMAL then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + player:sendCancelMessage("You are muted from the Help channel for using it inappropriately.") + return false + end + + if playerAccountType >= ACCOUNT_TYPE_TUTOR then + if string.sub(message, 1, 6) == "!mute " then + local targetName = string.sub(message, 7) + local target = Player(targetName) + if target ~= nil then + if playerAccountType > target:getAccountType() then + if not target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + target:addCondition(muted) + sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been muted by " .. player:getName() .. " for using Help Channel inappropriately.") + else + player:sendCancelMessage("That player is already muted.") + end + else + player:sendCancelMessage("You are not authorized to mute that player.") + end + else + player:sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + end + return false + elseif string.sub(message, 1, 8) == "!unmute " then + local targetName = string.sub(message, 9) + local target = Player(targetName) + if target ~= nil then + if playerAccountType > target:getAccountType() then + if target:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) then + target:removeCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_HELP) + sendChannelMessage(CHANNEL_HELP, TALKTYPE_CHANNEL_R1, target:getName() .. " has been unmuted by " .. player:getName() .. ".") + else + player:sendCancelMessage("That player is not muted.") + end + else + player:sendCancelMessage("You are not authorized to unmute that player.") + end + else + player:sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + end + return false + end + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_TUTOR or getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_TUTOR and not getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + if playerAccountType >= ACCOUNT_TYPE_TUTOR or getPlayerFlagValue(player, PlayerFlag_TalkOrangeHelpChannel) then + type = TALKTYPE_CHANNEL_O + else + type = TALKTYPE_CHANNEL_Y + end + end + end + return type +end diff --git a/data/chatchannels/scripts/ruleviolations.lua b/data/chatchannels/scripts/ruleviolations.lua new file mode 100644 index 0000000..ea52307 --- /dev/null +++ b/data/chatchannels/scripts/ruleviolations.lua @@ -0,0 +1,3 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER +end diff --git a/data/chatchannels/scripts/trade.lua b/data/chatchannels/scripts/trade.lua new file mode 100644 index 0000000..73caeb7 --- /dev/null +++ b/data/chatchannels/scripts/trade.lua @@ -0,0 +1,40 @@ +function canJoin(player) + return player:getVocation():getId() ~= VOCATION_NONE or player:getAccountType() >= ACCOUNT_TYPE_SENIORTUTOR +end + +local CHANNEL_TRADE = 6 + +local muted = Condition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT) +muted:setParameter(CONDITION_PARAM_SUBID, CHANNEL_TRADE) +muted:setParameter(CONDITION_PARAM_TICKS, 120000) + +function onSpeak(player, type, message) + if player:getAccountType() >= ACCOUNT_TYPE_GAMEMASTER then + if type == TALKTYPE_CHANNEL_Y then + return TALKTYPE_CHANNEL_O + end + return true + end + + if player:getLevel() == 1 then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if player:getCondition(CONDITION_CHANNELMUTEDTICKS, CONDITIONID_DEFAULT, CHANNEL_TRADE) then + player:sendCancelMessage("You may only place one offer in two minutes.") + return false + end + player:addCondition(muted) + + if type == TALKTYPE_CHANNEL_O then + if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/data/chatchannels/scripts/tutor.lua b/data/chatchannels/scripts/tutor.lua new file mode 100644 index 0000000..8db26a8 --- /dev/null +++ b/data/chatchannels/scripts/tutor.lua @@ -0,0 +1,21 @@ +function canJoin(player) + return player:getAccountType() >= ACCOUNT_TYPE_TUTOR +end + +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_SENIORTUTOR then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_SENIORTUTOR then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/data/chatchannels/scripts/worldchat.lua b/data/chatchannels/scripts/worldchat.lua new file mode 100644 index 0000000..04cf10c --- /dev/null +++ b/data/chatchannels/scripts/worldchat.lua @@ -0,0 +1,22 @@ +function onSpeak(player, type, message) + local playerAccountType = player:getAccountType() + if player:getLevel() == 1 and playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + player:sendCancelMessage("You may not speak into channels as long as you are on level 1.") + return false + end + + if type == TALKTYPE_CHANNEL_Y then + if playerAccountType >= ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_O + end + elseif type == TALKTYPE_CHANNEL_O then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER then + type = TALKTYPE_CHANNEL_Y + end + elseif type == TALKTYPE_CHANNEL_R1 then + if playerAccountType < ACCOUNT_TYPE_GAMEMASTER and not getPlayerFlagValue(player, PlayerFlag_CanTalkRedChannel) then + type = TALKTYPE_CHANNEL_Y + end + end + return type +end diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml new file mode 100644 index 0000000..a0aa661 --- /dev/null +++ b/data/creaturescripts/creaturescripts.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/data/creaturescripts/lib/creaturescripts.lua b/data/creaturescripts/lib/creaturescripts.lua new file mode 100644 index 0000000..6116bcc --- /dev/null +++ b/data/creaturescripts/lib/creaturescripts.lua @@ -0,0 +1 @@ +-- empty file -- diff --git a/data/creaturescripts/scripts/firstitems.lua b/data/creaturescripts/scripts/firstitems.lua new file mode 100644 index 0000000..0c3c75a --- /dev/null +++ b/data/creaturescripts/scripts/firstitems.lua @@ -0,0 +1,30 @@ +function onLogin(player) + if player:getLastLoginSaved() <= 0 then + -- Items + if player:getSex() == PLAYERSEX_FEMALE then + player:addItem(3562, 1, true, -1, CONST_SLOT_ARMOR) + else + player:addItem(3561, 1, true, -1, CONST_SLOT_ARMOR) + end + player:addItem(3270, 1, true, -1, CONST_SLOT_LEFT) + player:addItem(2920, 1, true, -1, CONST_SLOT_RIGHT) + + local container = Game.createItem(2853, 1) + container:addItem(3585, 1) + + player:addItemEx(container, true, CONST_SLOT_BACKPACK) + + -- Default Outfit + if player:getSex() == PLAYERSEX_FEMALE then + player:setOutfit({lookType = 136, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + else + player:setOutfit({lookType = 128, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + end + + local town = Town("Rookgaard") + player:teleportTo(town:getTemplePosition()) + player:setTown(town) + player:setDirection(DIRECTION_SOUTH) + end + return true +end diff --git a/data/creaturescripts/scripts/login.lua b/data/creaturescripts/scripts/login.lua new file mode 100644 index 0000000..0525a96 --- /dev/null +++ b/data/creaturescripts/scripts/login.lua @@ -0,0 +1,54 @@ +function onLogin(player) + local loginStr = "Welcome to " .. configManager.getString(configKeys.SERVER_NAME) .. "!" + if player:getLastLoginSaved() <= 0 then + loginStr = loginStr .. " Please choose your outfit." + player:sendOutfitWindow() + else + if loginStr ~= "" then + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + end + + loginStr = string.format("Your last visit on Nostalrius: %s.", os.date("%a %b %d %X %Y", player:getLastLoginSaved())) + end + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, loginStr) + + -- Promotion + if player:isPremium() then + if player:getVocation():getId() ~= 0 and player:getVocation():getId() < 5 and player:getStorageValue(30018) == 1 then + player:setVocation(player:getVocation():getId() + 4) + end + else + if player:getVocation():getId() ~= 0 and player:getVocation():getId() > 4 then + player:setVocation(player:getVocation():getId() - 4) + end + end + + -- Outfits + if not player:isPremium() then + if player:getSex() == PLAYERSEX_FEMALE then + local outfit = player:getOutfit() + if outfit.lookType > 139 then + player:setOutfit({lookType = 136, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + end + else + local outfit = player:getOutfit() + if outfit.lookType > 131 then + player:setOutfit({lookType = 128, lookHead = 78, lookBody = 106, lookLegs = 58, lookFeet = 95}) + end + end + end + + -- Premium system + if player:isPremium() then + player:setStorageValue(43434, 1) + elseif player:getStorageValue(43434) == 1 then + player:setStorageValue(43434, 0) + player:teleportTo({x = 32369, y = 32241, z = 7}) + player:setTown(Town("Thais")) + end + + -- Events + player:registerEvent("PlayerDeath") + player:registerEvent("kills") + return true +end diff --git a/data/creaturescripts/scripts/playerdeath.lua b/data/creaturescripts/scripts/playerdeath.lua new file mode 100644 index 0000000..7e7ff39 --- /dev/null +++ b/data/creaturescripts/scripts/playerdeath.lua @@ -0,0 +1,94 @@ +local deathListEnabled = true +local maxDeathRecords = 50 + +function onDeath(player, corpse, killer, mostDamageKiller, unjustified, mostDamageUnjustified) + local playerId = player:getId() + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are dead.") + + -- restart blessings values + player:setStorageValue(101,0) + player:setStorageValue(102,0) + player:setStorageValue(103,0) + player:setStorageValue(104,0) + player:setStorageValue(105,0) + + if not deathListEnabled then + return + end + + local byPlayer = 0 + local killerName + if killer ~= nil then + if killer:isPlayer() then + byPlayer = 1 + else + local master = killer:getMaster() + if master and master ~= killer and master:isPlayer() then + killer = master + byPlayer = 1 + end + end + killerName = killer:getName() + else + killerName = "field item" + end + + local byPlayerMostDamage = 0 + local mostDamageKillerName + if mostDamageKiller ~= nil then + if mostDamageKiller:isPlayer() then + byPlayerMostDamage = 1 + else + local master = mostDamageKiller:getMaster() + if master and master ~= mostDamageKiller and master:isPlayer() then + mostDamageKiller = master + byPlayerMostDamage = 1 + end + end + mostDamageName = mostDamageKiller:getName() + else + mostDamageName = "field item" + end + + local playerGuid = player:getGuid() + db.query("INSERT INTO `player_deaths` (`player_id`, `time`, `level`, `killed_by`, `is_player`, `mostdamage_by`, `mostdamage_is_player`, `unjustified`, `mostdamage_unjustified`) VALUES (" .. playerGuid .. ", " .. os.time() .. ", " .. player:getLevel() .. ", " .. db.escapeString(killerName) .. ", " .. byPlayer .. ", " .. db.escapeString(mostDamageName) .. ", " .. byPlayerMostDamage .. ", " .. (unjustified and 1 or 0) .. ", " .. (mostDamageUnjustified and 1 or 0) .. ")") + local resultId = db.storeQuery("SELECT `player_id` FROM `player_deaths` WHERE `player_id` = " .. playerGuid) + + local deathRecords = 0 + local tmpResultId = resultId + while tmpResultId ~= false do + tmpResultId = result.next(resultId) + deathRecords = deathRecords + 1 + end + + if resultId ~= false then + result.free(resultId) + end + + local limit = deathRecords - maxDeathRecords + if limit > 0 then + db.asyncQuery("DELETE FROM `player_deaths` WHERE `player_id` = " .. playerGuid .. " ORDER BY `time` LIMIT " .. limit) + end + + if byPlayer == 1 then + local targetGuild = player:getGuild() + targetGuild = targetGuild and targetGuild:getId() or 0 + if targetGuild ~= 0 then + local killerGuild = killer:getGuild() + killerGuild = killerGuild and killerGuild:getId() or 0 + if killerGuild ~= 0 and targetGuild ~= killerGuild and isInWar(playerId, killer:getId()) then + local warId = false + resultId = db.storeQuery("SELECT `id` FROM `guild_wars` WHERE `status` = 1 AND ((`guild1` = " .. killerGuild .. " AND `guild2` = " .. targetGuild .. ") OR (`guild1` = " .. targetGuild .. " AND `guild2` = " .. killerGuild .. "))") + if resultId ~= false then + warId = result.getDataInt(resultId, "id") + result.free(resultId) + end + + if warId ~= false then + db.asyncQuery("INSERT INTO `guildwar_kills` (`killer`, `target`, `killerguild`, `targetguild`, `time`, `warid`) VALUES (" .. db.escapeString(killerName) .. ", " .. db.escapeString(player:getName()) .. ", " .. killerGuild .. ", " .. targetGuild .. ", " .. os.time() .. ", " .. warId .. ")") + end + end + end + end +end diff --git a/data/global.lua b/data/global.lua new file mode 100644 index 0000000..89a84ec --- /dev/null +++ b/data/global.lua @@ -0,0 +1,43 @@ +dofile('data/lib/lib.lua') + +function getDistanceBetween(firstPosition, secondPosition) + local xDif = math.abs(firstPosition.x - secondPosition.x) + local yDif = math.abs(firstPosition.y - secondPosition.y) + local posDif = math.max(xDif, yDif) + if firstPosition.z ~= secondPosition.z then + posDif = posDif + 15 + end + return posDif +end + +function getFormattedWorldTime() + local worldTime = getWorldTime() + local hours = math.floor(worldTime / 60) + + local minutes = worldTime % 60 + if minutes < 10 then + minutes = '0' .. minutes + end + return hours .. ':' .. minutes +end + +string.split = function(str, sep) + local res = {} + for v in str:gmatch("([^" .. sep .. "]+)") do + res[#res + 1] = v + end + return res +end + +string.trim = function(str) + return str:match'^()%s*$' and '' or str:match'^%s*(.*%S)' +end + +table.contains = function(array, value) + for _, targetColumn in pairs(array) do + if targetColumn == value then + return true + end + end + return false +end \ No newline at end of file diff --git a/data/globalevents/globalevents.xml b/data/globalevents/globalevents.xml new file mode 100644 index 0000000..42a865e --- /dev/null +++ b/data/globalevents/globalevents.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/data/globalevents/lib/globalevents.lua b/data/globalevents/lib/globalevents.lua new file mode 100644 index 0000000..6de6c7d --- /dev/null +++ b/data/globalevents/lib/globalevents.lua @@ -0,0 +1 @@ +-- empty file -- \ No newline at end of file diff --git a/data/globalevents/scripts/record.lua b/data/globalevents/scripts/record.lua new file mode 100644 index 0000000..1c6a492 --- /dev/null +++ b/data/globalevents/scripts/record.lua @@ -0,0 +1,4 @@ +function onRecord(current, old) + addEvent(broadcastMessage, 150, "New record: " .. current .. " players are logged in.", MESSAGE_STATUS_DEFAULT) + return true +end \ No newline at end of file diff --git a/data/globalevents/scripts/serversave.lua b/data/globalevents/scripts/serversave.lua new file mode 100644 index 0000000..887d672 --- /dev/null +++ b/data/globalevents/scripts/serversave.lua @@ -0,0 +1,33 @@ +local shutdownAtServerSave = true +local cleanMapAtServerSave = false + +local function serverSave() + if shutdownAtServerSave then + Game.setGameState(GAME_STATE_SHUTDOWN) + else + Game.setGameState(GAME_STATE_CLOSED) + + if cleanMapAtServerSave then + cleanMap() + end + + Game.setGameState(GAME_STATE_NORMAL) + end +end + +local function secondServerSaveWarning() + broadcastMessage("Server is saving game in one minute.\nPlease log out.", MESSAGE_STATUS_WARNING) + addEvent(serverSave, 60000) +end + +local function firstServerSaveWarning() + broadcastMessage("Server is saving game in 3 minutes.\nPlease come back in 10 minutes.", MESSAGE_STATUS_WARNING) + addEvent(secondServerSaveWarning, 120000) +end + +function onTime(interval) + broadcastMessage("Server is saving game in 5 minutes.\nPlease come back in 10 minutes.", MESSAGE_STATUS_WARNING) + Game.setGameState(GAME_STATE_STARTUP) + addEvent(firstServerSaveWarning, 120000) + return not shutdownAtServerSave +end diff --git a/data/globalevents/scripts/startup.lua b/data/globalevents/scripts/startup.lua new file mode 100644 index 0000000..5910452 --- /dev/null +++ b/data/globalevents/scripts/startup.lua @@ -0,0 +1,50 @@ +function onStartup() + math.randomseed(os.mtime()) + + db.query("TRUNCATE TABLE `players_online`") + db.asyncQuery("DELETE FROM `guild_wars` WHERE `status` = 0") + db.asyncQuery("DELETE FROM `players` WHERE `deletion` != 0 AND `deletion` < " .. os.time()) + db.asyncQuery("DELETE FROM `ip_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time()) + + -- Move expired bans to ban history + local resultId = db.storeQuery("SELECT * FROM `account_bans` WHERE `expires_at` != 0 AND `expires_at` <= " .. os.time()) + if resultId ~= false then + repeat + local accountId = result.getDataInt(resultId, "account_id") + db.asyncQuery("INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" .. accountId .. ", " .. db.escapeString(result.getDataString(resultId, "reason")) .. ", " .. result.getDataLong(resultId, "banned_at") .. ", " .. result.getDataLong(resultId, "expires_at") .. ", " .. result.getDataInt(resultId, "banned_by") .. ")") + db.asyncQuery("DELETE FROM `account_bans` WHERE `account_id` = " .. accountId) + until not result.next(resultId) + result.free(resultId) + end + + -- Check house auctions + local resultId = db.storeQuery("SELECT `id`, `highest_bidder`, `last_bid`, (SELECT `balance` FROM `players` WHERE `players`.`id` = `highest_bidder`) AS `balance` FROM `houses` WHERE `owner` = 0 AND `bid_end` != 0 AND `bid_end` < " .. os.time()) + if resultId ~= false then + repeat + local house = House(result.getDataInt(resultId, "id")) + if house ~= nil then + local highestBidder = result.getDataInt(resultId, "highest_bidder") + local balance = result.getDataLong(resultId, "balance") + local lastBid = result.getDataInt(resultId, "last_bid") + if balance >= lastBid then + db.query("UPDATE `players` SET `balance` = " .. (balance - lastBid) .. " WHERE `id` = " .. highestBidder) + house:setOwnerGuid(highestBidder) + end + db.asyncQuery("UPDATE `houses` SET `last_bid` = 0, `bid_end` = 0, `highest_bidder` = 0, `bid` = 0 WHERE `id` = " .. house:getId()) + end + until not result.next(resultId) + result.free(resultId) + end + + -- Remove murders that are more than 60 days old + local resultId = db.storeQuery("SELECT * FROM `player_murders` WHERE `date` <= " .. os.time() - 60 * 24 * 60 * 60) + if resultId ~= false then + repeat + local playerId = result.getDataInt(resultId, "player_id") + local id = result.getDataLong(resultId, "id") + + db.asyncQuery("DELETE FROM `player_murders` WHERE `player_id` = " .. playerId .. " AND `id` = " .. id) + until not result.next(resultId) + result.free(resultId) + end +end diff --git a/data/items/items.srv b/data/items/items.srv new file mode 100644 index 0000000..b822f93 --- /dev/null +++ b/data/items/items.srv @@ -0,0 +1,23158 @@ +# items.srv - Tibia Item definitions +# --- begin of server specific object types --- + +TypeID = 1 +Name = "water" + +TypeID = 2 +Name = "wine" + +TypeID = 3 +Name = "beer" + +TypeID = 4 +Name = "mud" + +TypeID = 5 +Name = "blood" + +TypeID = 6 +Name = "slime" + +TypeID = 7 +Name = "oil" + +TypeID = 8 +Name = "urine" + +TypeID = 9 +Name = "milk" + +TypeID = 10 +Name = "manafluid" + +TypeID = 11 +Name = "lifefluid" + +TypeID = 12 +Name = "lemonade" + +# --- end of server specific object types --- + +TypeID = 100 +Name = "void" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 101 +Name = "earth" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 102 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 103 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 104 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 105 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 106 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 107 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 108 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 109 +Name = "flowers" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 110 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 111 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 112 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 113 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 114 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 115 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 116 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 117 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 118 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 119 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 120 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 121 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 122 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 123 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 124 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 125 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 126 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 127 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 128 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 129 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 130 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 131 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 132 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 133 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 134 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 135 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 136 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 137 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 138 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 139 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 140 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 141 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 142 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 143 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 144 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 145 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 146 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 147 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 148 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 149 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 150 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 151 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 152 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 153 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=125} + +TypeID = 154 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 155 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 156 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 157 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 158 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 159 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 160 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 161 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 162 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 163 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 164 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 165 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 166 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 167 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 168 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 169 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 170 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 171 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 172 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 173 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 174 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 175 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 176 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 177 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 178 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 179 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 180 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 181 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 182 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 183 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 184 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 185 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 186 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 187 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 188 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 189 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 190 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 191 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 192 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 193 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 194 +Name = "dirt" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 195 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 196 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 197 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 198 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 199 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 200 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 201 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 202 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 203 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 204 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 205 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 206 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 207 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 208 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 209 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 210 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 211 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 212 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 213 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 214 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 215 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 216 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 217 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 218 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 219 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 220 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 221 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 222 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 223 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=145} + +TypeID = 224 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 225 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 226 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 227 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 228 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 229 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 230 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 231 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 232 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 233 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 234 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 235 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 236 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 237 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 238 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 239 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 240 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 241 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 242 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 243 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=155} + +TypeID = 244 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 245 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 246 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 247 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 248 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 249 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 250 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 251 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 252 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 253 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 254 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 255 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 256 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 257 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 258 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 259 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 260 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 261 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 262 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 263 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 264 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 265 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 266 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 267 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 268 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 269 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 270 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 271 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 272 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 273 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 274 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 275 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 276 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 277 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 278 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 279 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 280 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 281 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 282 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 283 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 284 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 285 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 286 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 287 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 288 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 289 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 290 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 291 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 292 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 293 +Name = "grass" +Flags = {Bank,,CollisionEvent,Unmove} +Attributes = {Waypoints=150} + +TypeID = 294 +Name = "a pitfall" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=150,ExpireTarget=293,TotalExpireTime=300} + +TypeID = 295 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 296 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 297 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 298 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 299 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 300 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 301 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 302 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 303 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 304 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 305 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 306 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 307 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 308 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 309 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 310 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 311 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 312 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 313 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 314 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 315 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 316 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 317 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 318 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 319 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 320 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 321 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 322 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 323 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 324 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 325 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 326 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 327 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 328 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 329 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 330 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 331 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 332 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 333 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 334 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 335 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 336 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 337 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 338 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 339 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 340 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 341 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 342 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 343 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 344 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 345 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 346 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 347 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 348 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 349 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 350 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=135} + +TypeID = 351 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 352 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 353 +Name = "dirt floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 354 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 355 +Name = "muddy floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200,FluidSource=MUD} + +TypeID = 356 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 357 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 358 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 359 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 360 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 361 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 362 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 363 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 364 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 365 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 366 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 367 +Name = "a dirt wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 368 +Name = "earth ground" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 369 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 370 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=110} + +TypeID = 371 +Name = "dirt floor" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 372 +Name = "muddy floor" +Flags = {Bank,UseEvent,Unmove,Disguise} +Attributes = {Waypoints=200,FluidSource=MUD,DisguiseTarget=355} + +TypeID = 373 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 374 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 375 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 376 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 377 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 378 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 379 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 380 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 381 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 382 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 383 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 384 +Name = "a stone wall" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 385 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 386 +Name = "dirt floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=120} + +TypeID = 387 +Name = "a small hole" +Description = "It seems too narrow to climb through" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=130} + +TypeID = 388 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 389 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 390 +Name = "a lava hole" +Description = "It seems to be inactive" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 391 +Name = "a lava hole" +Description = "It emits heat and light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=215} + +TypeID = 392 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 393 +Name = "stalagmites" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 394 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=110,ExpireTarget=372,TotalExpireTime=300} + +TypeID = 395 +Name = "dirt floor" +Flags = {Bank,SeparationEvent,Unmove,Disguise} +Attributes = {Waypoints=140,DisguiseTarget=353} + +TypeID = 396 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 397 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 398 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 399 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 400 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 401 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 402 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 403 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 404 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 405 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 406 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 407 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 408 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 409 +Name = "white marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 410 +Name = "black marble floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 411 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 412 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 413 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 414 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 415 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 416 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 417 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 418 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 419 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 420 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 421 +Name = "sandy floor" +Description = "There is a hole in the ceiling" +Flags = {Bank,UseEvent,ForceUse,Unmove} +Attributes = {Waypoints=100} + +TypeID = 422 +Name = "a sandstone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 423 +Name = "tiled floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 424 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 425 +Name = "sandstone floor" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=70} + +TypeID = 426 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=424} + +TypeID = 427 +Name = "sandstone floor" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=10,DisguiseTarget=425} + +TypeID = 428 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 429 +Name = "a stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 430 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 431 +Name = "a stone tile" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 432 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 433 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 434 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 435 +Name = "a sewer grate" +Flags = {UseEvent,Unmove} + +TypeID = 436 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 437 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 438 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 439 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 440 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 441 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 442 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 443 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 444 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 445 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 446 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 447 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 448 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 449 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 450 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 451 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 452 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 453 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 454 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 455 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 456 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 457 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 458 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 459 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 460 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 461 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 462 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 463 +Name = "a white stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 464 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 465 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 466 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 467 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 468 +Name = "nothing special" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=470} + +TypeID = 469 +Name = "stairs" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=95} + +TypeID = 470 +Name = "nothing special" +Flags = {Bank,Unmove} +Attributes = {Waypoints=95} + +TypeID = 471 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 472 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 473 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 474 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Disguise} +Attributes = {Waypoints=95,DisguiseTarget=469} + +TypeID = 475 +Name = "a closed trapdoor" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 476 +Name = "an open trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Expire} +Attributes = {Waypoints=100,ExpireTarget=475,TotalExpireTime=2} + +TypeID = 477 +Name = "a pedestal" +Flags = {Unmove,Avoid,Height} + +TypeID = 478 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 479 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 480 +Name = "a sandstone wall" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 481 +Name = "stone floor" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 482 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 483 +Name = "a trapdoor" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 484 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 485 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 486 +Name = "wooden floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 487 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 488 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 489 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 490 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 491 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 492 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 493 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 494 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 495 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 496 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 497 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 498 +Name = "wooden floor" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=200} + +TypeID = 499 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 500 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 501 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 502 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 503 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 504 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 505 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 506 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 507 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 508 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 509 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 510 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 511 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 512 +Name = "a large stone carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 513 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 514 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 515 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 516 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 517 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 518 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 519 +Name = "a carved stone tile" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 520 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 521 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 522 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 523 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 524 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 525 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 526 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 527 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 528 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 529 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 530 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 531 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 532 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 533 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 534 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 535 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 536 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 537 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 538 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 539 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 540 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 541 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 542 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 543 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 544 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 545 +Name = "ornamented floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 546 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 547 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 548 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 549 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 550 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 551 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 552 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 553 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 554 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 555 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 556 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 557 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 558 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 559 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 560 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 561 +Name = "a snake ornament" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 562 +Name = "stone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 563 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 564 +Name = "wooden floor" +Description = "It seems to be a switch" +Flags = {Bank,SeparationEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 565 +Name = "stone floor" +Flags = {Clip,Unmove} + +TypeID = 566 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 567 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 568 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 569 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 570 +Name = "stone floor" +Flags = {Clip,Unpass,Unmove,Unlay} + +TypeID = 571 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 572 +Name = "wooden floor" +Flags = {Unmove} + +TypeID = 573 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 574 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 575 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 576 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 577 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 578 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 579 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 580 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 581 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 582 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 583 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 584 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 585 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 586 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 587 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 588 +Name = "a broken stone tile" +Flags = {Unmove} + +TypeID = 589 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 590 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 591 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 592 +Name = "wooden floor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 593 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 594 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=160,ExpireTarget=593,TotalExpireTime=300} + +TypeID = 595 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 596 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 597 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 598 +Name = "a strange carving" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 599 +Name = "a strange carving" +Flags = {Bank,CollisionEvent,Unmove} +Attributes = {Waypoints=100} + +TypeID = 600 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 601 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 602 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 603 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 604 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 605 +Name = "a ramp" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=150} + +TypeID = 606 +Name = "a loose stone pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=170} + +TypeID = 607 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=606,TotalExpireTime=300} + +TypeID = 608 +Name = "a loose ice pile" +Flags = {Bank,UseEvent,Unmove,Avoid} +Attributes = {Waypoints=120} + +TypeID = 609 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=120,ExpireTarget=608,TotalExpireTime=300} + +TypeID = 610 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 611 +Name = "a snow heap" +Flags = {UseEvent,Unmove,Avoid} + +TypeID = 612 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 613 +Name = "a ramp" +Flags = {Clip,Unmove} + +TypeID = 614 +Name = "sand" +Flags = {UseEvent,Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 615 +Name = "a hole" +Flags = {Bank,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {Waypoints=170,ExpireTarget=614,TotalExpireTime=30} + +TypeID = 616 +Name = "sand" +Flags = {Bank,Unmove,Disguise} +Attributes = {Waypoints=160,DisguiseTarget=231} + +TypeID = 617 +Name = "sand" +Flags = {Bank,Unmove,Expire,Disguise} +Attributes = {Waypoints=160,ExpireTarget=616,TotalExpireTime=4000,DisguiseTarget=231} + +TypeID = 618 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=618,TotalExpireTime=2200} + +TypeID = 621 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=620} + +TypeID = 622 +Name = "water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 623 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 624 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 625 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 626 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 627 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 628 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 629 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 630 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 631 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 632 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 633 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 634 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=1} + +TypeID = 635 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 636 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 637 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 638 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 639 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 640 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 641 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 642 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 643 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 644 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 645 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 646 +Name = "water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 647 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 648 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 649 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 650 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 651 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 652 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 653 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 654 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 655 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 656 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 657 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 658 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 659 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 660 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 661 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 662 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 663 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 664 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 665 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 666 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 667 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 668 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 669 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 670 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 671 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 672 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 673 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 674 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 675 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 676 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 677 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 678 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 679 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 680 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 681 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 682 +Name = "lava" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,Brightness=3,LightColor=215} + +TypeID = 683 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 684 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 685 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 686 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 687 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 688 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 689 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 690 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 691 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 692 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 693 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 694 +Name = "lava" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=215} + +TypeID = 695 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 696 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 697 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 698 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 699 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 700 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 701 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 702 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 703 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 704 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 705 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 706 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 707 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 708 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 709 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 710 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 711 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 712 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 713 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 714 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 715 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 716 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 717 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=150} + +TypeID = 718 +Name = "grass" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 719 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 720 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 721 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 722 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 723 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 724 +Name = "sand" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 725 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 726 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 727 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 728 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 729 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 730 +Name = "lava" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,Brightness=4,LightColor=193} + +TypeID = 731 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 732 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 733 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 734 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 735 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 736 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 737 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 738 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 739 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 740 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 741 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 742 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 743 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 744 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 745 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 746 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 747 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 748 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 749 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 750 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 751 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 752 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 753 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 754 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 755 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 756 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 757 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 758 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 759 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 760 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 761 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 762 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 763 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 764 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 765 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 766 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 767 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 768 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 769 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 770 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 771 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 772 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 773 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 774 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 775 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 776 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 777 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 778 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 779 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 780 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 781 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 782 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 783 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 784 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 785 +Name = "swamp" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 786 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 787 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 788 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 789 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 790 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 791 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 792 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 793 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 794 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 795 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 796 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 797 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 798 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 799 +Name = "snow" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 800 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 801 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 802 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 803 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 804 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 805 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 806 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 807 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 808 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 809 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 810 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 811 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 812 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 813 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 814 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 815 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 816 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 817 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 818 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 819 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 820 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 821 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 822 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 823 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 824 +Name = "ice" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 825 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 826 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 827 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 828 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 829 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 830 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 831 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 832 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 833 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 834 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 835 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 836 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 837 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 838 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 839 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 840 +Name = "tar" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 841 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 842 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 843 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 844 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 845 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 846 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 847 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 848 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 849 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 850 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 851 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 852 +Name = "tar" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=0} + +TypeID = 853 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 854 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 855 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 856 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 857 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 858 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 859 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 860 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 861 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 862 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 863 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 864 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 865 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 866 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 867 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 868 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 869 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 870 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 871 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 872 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 873 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 874 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 875 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 876 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 877 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 878 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 879 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 880 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 881 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 882 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 883 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 884 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 885 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 886 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 887 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 888 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 889 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 890 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 891 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 892 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 893 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 894 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 895 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 896 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 897 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 898 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 899 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 900 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 901 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 902 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 903 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 904 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 905 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 906 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 907 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 908 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 909 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 910 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 911 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 912 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 913 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 914 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 915 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 916 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 917 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 918 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 919 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 920 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 921 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 922 +Name = "cobbled pavement" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 923 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 924 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 925 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 926 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 927 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 928 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 929 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 930 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 931 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 932 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 933 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 934 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 935 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 936 +Name = "sandstone floor" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 937 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 938 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 939 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 940 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 941 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 942 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 943 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 944 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 945 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 946 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 947 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 948 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 949 +Name = "a road" +Flags = {Bank,Unmove} +Attributes = {Waypoints=110} + +TypeID = 950 +Name = "soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 951 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 952 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 953 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 954 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 955 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 956 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 957 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 958 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 959 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 960 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 961 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 962 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 963 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 964 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 965 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 966 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 967 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 968 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 969 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 970 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 971 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 972 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 973 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 974 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 975 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 976 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 977 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 978 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 979 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 980 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 981 +Name = "sand" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 982 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 983 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 984 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 985 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 986 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 987 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 988 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 989 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 990 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 991 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 992 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 993 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 994 +Name = "dry earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 995 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 996 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 997 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 998 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 999 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1000 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1001 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1002 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=130} + +TypeID = 1003 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1004 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1005 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1006 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1007 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1008 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1009 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1010 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1011 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1012 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1013 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1014 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1015 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1016 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1017 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1018 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 1019 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1020 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1021 +Name = "earth" +Flags = {Bank,Unmove} +Attributes = {Waypoints=140} + +TypeID = 1022 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1023 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1024 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1025 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1026 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1027 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1028 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1029 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1030 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1031 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1032 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1033 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1034 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1035 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1036 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1037 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1038 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1039 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1040 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1041 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1042 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1043 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1044 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1045 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1046 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1047 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1048 +Name = "jungle grass " +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1049 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1050 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1051 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1052 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1053 +Name = "jungle grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1054 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1055 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1056 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1057 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1058 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1059 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1060 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1061 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1062 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1063 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1064 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1065 +Name = "jungle grass" +Flags = {Clip,Unmove} + +TypeID = 1066 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 1067 +Name = "a pitfall" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Expire} +Attributes = {ExpireTarget=1066,TotalExpireTime=75} + +TypeID = 1068 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1069 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1070 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1071 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1072 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1073 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1074 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1075 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1076 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1077 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1078 +Name = "an ant trail" +Flags = {Clip,Unmove} + +TypeID = 1079 +Name = "an ant-hill" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1080 +Name = "an earth hole" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} + +TypeID = 1081 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1082 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1083 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1084 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1085 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1086 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1087 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1088 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1089 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1090 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1091 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1092 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1093 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1094 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1095 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1096 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1097 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1098 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 1099 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay,Disguise} +Attributes = {Waypoints=0,DisguiseTarget=1128} + +TypeID = 1100 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1101 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1102 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1103 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1104 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1105 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1106 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1107 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1108 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1109 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1110 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1111 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 1112 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1113 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1114 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1115 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1116 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1117 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1118 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1119 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1120 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1121 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1122 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1123 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1124 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1125 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1126 +Name = "a mountain" +Flags = {Top,Unmove} + +TypeID = 1127 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1128 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1129 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1130 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1131 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1132 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1133 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1134 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1135 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1136 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1137 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1138 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1139 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1140 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1141 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1142 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1143 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1144 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1145 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1146 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1147 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1148 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1149 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1150 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1151 +Name = "a stone" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1152 +Name = "a flat roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1153 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1154 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1155 +Name = "a flat roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1156 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 1157 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1158 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1159 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1160 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1161 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1162 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1163 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1164 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1165 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1166 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1167 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1168 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1169 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1170 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1171 +Name = "a tiled roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1172 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1173 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1174 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1175 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1176 +Name = "a tiled roof" +Flags = {Unpass,Unmove} + +TypeID = 1177 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1178 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1179 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1180 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1181 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1182 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1183 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1184 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1185 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1186 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1187 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1188 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1189 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1190 +Name = "a wooden roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1191 +Name = "a wooden roof" +Flags = {Unpass,Unmove} + +TypeID = 1192 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1193 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1194 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1195 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1196 +Name = "a wooden roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1197 +Name = "a dried grass roof" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160} + +TypeID = 1198 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1199 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1200 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1201 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1202 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1203 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1204 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1205 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1206 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1207 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1208 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1209 +Name = "a dried grass roof" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1210 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1211 +Name = "a chess board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1212 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1213 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1214 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1215 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1216 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1217 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1218 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1219 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1220 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1221 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1222 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1223 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1224 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1225 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1226 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1227 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1228 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1229 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1230 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1231 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1232 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1233 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1234 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1235 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1236 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1237 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1238 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1239 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1240 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1241 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1242 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1243 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1244 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1245 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1246 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1247 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1248 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1249 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1250 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1251 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1252 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1253 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1254 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1255 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1256 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1257 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1258 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1259 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1260 +Name = "a mill board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1261 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1262 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1263 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1264 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1265 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1266 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1267 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1268 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1269 +Name = "a tic-tac-toe board" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 1270 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1271 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1272 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1273 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1274 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1275 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1276 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1277 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1278 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1279 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1280 +Name = "a brick wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1281 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1282 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1283 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1284 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1285 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1286 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1287 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1288 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1289 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1290 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1291 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1292 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1293 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1294 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1295 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1296 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1297 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1298 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1299 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1300 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1301 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1302 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1303 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1304 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1305 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1306 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1307 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1308 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1309 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1310 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1311 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1312 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1313 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1314 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1315 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1316 +Name = "sandstone" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 1317 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1318 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1319 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1320 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1321 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1322 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1323 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1324 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1325 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1326 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1327 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1328 +Name = "sandstone" +Flags = {Clip,Unmove} + +TypeID = 1329 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1330 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1331 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1332 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1333 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1334 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1335 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1336 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1337 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1338 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1339 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1340 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1341 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1342 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1343 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1344 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1345 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1346 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1347 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1348 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1349 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1350 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1351 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1352 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1353 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1354 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1355 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1356 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1357 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1358 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1359 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1360 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1361 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1362 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1363 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1364 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1365 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1366 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1367 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1368 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1369 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1370 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1371 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1372 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1373 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1374 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1375 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1376 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1377 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1378 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1379 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1380 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1381 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1382 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1383 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1384 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1385 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1386 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1387 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1388 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1389 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1390 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1391 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1392 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1393 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1394 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1395 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1396 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1397 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1398 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1399 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1400 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1401 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1402 +Name = "a wall fountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1403 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1404 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1405 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1406 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1407 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1408 +Name = "an oriental wall" +Flags = {Top,Unmove} + +TypeID = 1409 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1410 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1411 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1412 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1413 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1414 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1415 +Name = "a paravent" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1416 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1417 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1418 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1419 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1420 +Name = "a paravent wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1421 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1422 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1423 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1424 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1425 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1426 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1427 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1428 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1429 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1430 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1431 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1432 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1433 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1434 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1435 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1436 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1437 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1438 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1439 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1440 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1441 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1442 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1443 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1444 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1445 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1446 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1447 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1448 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1449 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 1450 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1451 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1452 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1453 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1454 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1455 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1456 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1457 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1458 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1459 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1460 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1461 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1462 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1463 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1464 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1465 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1466 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1467 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1468 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1469 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1470 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1471 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1472 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1473 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1474 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1475 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1476 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1477 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1478 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1479 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1480 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1481 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1482 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1483 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1484 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1485 +Name = "a stone wall" +Flags = {Top,Unmove} + +TypeID = 1486 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1487 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1488 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1489 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1490 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1491 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1492 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1493 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1494 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1495 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1496 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1497 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1498 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1499 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1500 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1501 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1502 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1503 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1504 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1505 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1506 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1507 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1508 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1509 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1510 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1511 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1512 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1513 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1514 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1515 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1516 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1517 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1518 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1519 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1520 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1521 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1522 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1523 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1524 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1525 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1526 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1527 +Name = "a bamboo fence" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1528 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1529 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1530 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1531 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1532 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1533 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1534 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1535 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1536 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 1537 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1538 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1539 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 1540 +Name = "a bamboo wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1541 +Name = "a bamboo wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1542 +Name = "a bamboo palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1543 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1544 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1545 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1546 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1547 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1548 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1549 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1550 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1551 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1552 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1553 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1554 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1555 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1556 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1557 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1558 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1559 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1560 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1561 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1562 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1563 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1564 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1565 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1566 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1567 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1568 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1569 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1570 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1571 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1572 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1573 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1574 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1575 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1576 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1577 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1578 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1579 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1580 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1581 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1582 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1583 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1584 +Name = "a broken stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1585 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1586 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1587 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1588 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1589 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1590 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1591 +Name = "a grass wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1592 +Name = "a grass wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1593 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1594 +Name = "a grass archway" +Flags = {Top,Unmove} + +TypeID = 1595 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1596 +Name = "a liane" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1597 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1598 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1599 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1600 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1601 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1602 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1603 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1604 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1605 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1606 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1607 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1608 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1609 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1610 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1611 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1612 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1613 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1614 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1615 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1616 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1617 +Name = "a palisade" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1618 +Name = "a temple wall" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1619 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1620 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1621 +Name = "a palisade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1622 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1623 +Name = "a stone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1624 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1625 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1626 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1627 +Name = "an archway" +Flags = {Top,Unmove} + +TypeID = 1628 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1629 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1630 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1631 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1632 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1633 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1634 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1635 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1636 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1637 +Name = "a buttress" +Flags = {Top,Unmove} + +TypeID = 1638 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1639 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1640 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1641 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1642 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1643 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1644 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1645 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1646 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1647 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1648 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1649 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1650 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1651 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1652 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1653 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1654 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1655 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1656 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1657 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1658 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1659 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1660 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1661 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1662 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1663 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1664 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1665 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1666 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1667 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1668 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1669 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1670 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1671 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1672 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1673 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1674 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1675 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1676 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1677 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1678 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1679 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1680 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1681 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1682 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1683 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1684 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1685 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1686 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1687 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1688 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1689 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1690 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1691 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1692 +Name = "a closed door" +Description = "It is locked" +Flags = {Bottom,UseEvent,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1693 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1694 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1695 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 1696 +Name = "a gate of expertise" +Description = "Only the worthy may pass" +Flags = {Door,Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1697 +Name = "a gate of expertise" +Flags = {Door,Top,SeparationEvent,Unmove} + +TypeID = 1698 +Name = "a closed door" +Description = "The door seems to be sealed against unwanted intruders" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1699 +Name = "an open door" +Flags = {Top,SeparationEvent,Door,Unmove} + +TypeID = 1700 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1701 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1702 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1703 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1704 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1705 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1706 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1707 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1708 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1709 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1710 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1711 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1712 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1713 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1714 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1715 +Name = "water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1716 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1717 +Name = "a waterfall" +Flags = {Unmove} + +TypeID = 1718 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1719 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1720 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1721 +Name = "a waterfall" +Flags = {Clip,CollisionEvent,Unmove} + +TypeID = 1722 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1723 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1724 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1725 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1726 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1727 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1728 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1729 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1730 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1731 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1732 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1733 +Name = "swamp" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 1734 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1735 +Name = "a framework window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1736 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1737 +Name = "a brick window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1738 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1739 +Name = "a stone wall window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1740 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1741 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1742 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1743 +Name = "an oriental window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1744 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1745 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1746 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1747 +Name = "a sandstone window" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1748 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1749 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1750 +Name = "a sail" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 1751 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1752 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1753 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1754 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1755 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1756 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1757 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1758 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1759 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1760 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1761 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1762 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1763 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1764 +Name = "a small boat" +Flags = {Bottom,Unmove} + +TypeID = 1765 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1766 +Name = "a small sail" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1767 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1768 +Name = "a paddle" +Flags = {Unmove} + +TypeID = 1769 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1770 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 1771 +Name = "a drawbridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=90} + +TypeID = 1772 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1773 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1774 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1775 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1776 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1777 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1778 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1779 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1780 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=41000} + +TypeID = 1781 +Name = "a small stone" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=360,Range=7,Attack=10,Defense=0,MissileEffect=10,Fragility=7} + +TypeID = 1782 +Name = "a stone" +Flags = {Take} +Attributes = {Weight=78000} + +TypeID = 1783 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1784 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1785 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1786 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1787 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1788 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1789 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1790 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1791 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1792 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1793 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1794 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1795 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1796 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1797 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1798 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1799 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1800 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1801 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1802 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1803 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1804 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1805 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1806 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1807 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1808 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1809 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1810 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1811 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1812 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1813 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1814 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1815 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1816 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1817 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1818 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1819 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1820 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1821 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1822 +Name = "a stone pile" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1823 +Name = "a stone pile" +Flags = {Unmove} + +TypeID = 1824 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1825 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1826 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1827 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1828 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1829 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1830 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1831 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1832 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1833 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1834 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1835 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1836 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1837 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1838 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1839 +Name = "debris" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1840 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1841 +Name = "a blue shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1842 +Name = "a red shrine stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1843 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1844 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1845 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1846 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1847 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1848 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1849 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1850 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1851 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1852 +Name = "stones" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1853 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1854 +Name = "stones" +Flags = {Bottom,Unmove} + +TypeID = 1855 +Name = "stones" +Flags = {Bottom,Unmove} + +TypeID = 1856 +Name = "a stone" +Flags = {Bottom,Unmove} + +TypeID = 1857 +Name = "stones" +Flags = {Bottom,Unmove} + +TypeID = 1858 +Name = "stones" +Flags = {Bottom,Unmove} + +TypeID = 1859 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1860 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1861 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1862 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1863 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1864 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1865 +Name = "a stone" +Flags = {Bottom,Unmove} + +TypeID = 1866 +Name = "a stone" +Flags = {Bottom,Unmove} + +TypeID = 1867 +Name = "a stone" +Flags = {Bottom,Unmove} + +TypeID = 1868 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1869 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1870 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1871 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1872 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1873 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1874 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1875 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1876 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1877 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1878 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1879 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1880 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1881 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1882 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1883 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1884 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1885 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1886 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1887 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1888 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1889 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1890 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1891 +Name = "a mossy stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1892 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1893 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1894 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1895 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1896 +Name = "debris" +Flags = {Bottom,Unmove} + +TypeID = 1897 +Name = "debris" +Flags = {Unmove} + +TypeID = 1898 +Name = "debris" +Flags = {Unmove} + +TypeID = 1899 +Name = "debris" +Flags = {Unmove} + +TypeID = 1900 +Name = "debris" +Flags = {Unmove} + +TypeID = 1901 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1902 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1903 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1904 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1905 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1906 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1907 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1908 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1909 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1910 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1911 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1912 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1913 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1914 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1915 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1916 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1917 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1918 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1919 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1920 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1921 +Name = "a painted stone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1922 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1923 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1924 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1925 +Name = "a fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1926 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1927 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1928 +Name = "a water basin" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1929 +Name = "a water basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1930 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1931 +Name = "a draw well" +Flags = {Bottom,UseEvent,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1932 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1933 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1934 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1935 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1936 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1937 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1938 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1939 +Name = "an oriental well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1940 +Name = "a small basin" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 1941 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1942 +Name = "a water wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1943 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1944 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1945 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1946 +Name = "a millstone" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 1947 +Name = "stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1948 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1949 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 1950 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1951 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1952 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1953 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1954 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1955 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1956 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1957 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1958 +Name = "wooden stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1959 +Name = "a mystic flame" +Description = "You feel drawn to the mesmerizing light" +Flags = {Bottom,CollisionEvent,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=173} + +TypeID = 1960 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1961 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1962 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1963 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1964 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1965 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1966 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1967 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1968 +Name = "a ladder" +Flags = {Bottom,UseEvent,ForceUse,Unmove} + +TypeID = 1969 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1970 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1971 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1972 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1973 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1974 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1975 +Name = "a ramp" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1976 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 1977 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1978 +Name = "stone stairs" +Flags = {Bottom,CollisionEvent,Unmove,Avoid,Height} + +TypeID = 1979 +Name = "a grave" +Flags = {Bottom,Unmove,AllowDistRead} + +TypeID = 1980 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1981 +Name = "a grave stone" +Flags = {Bottom,Unpass,Unmove,AllowDistRead} + +TypeID = 1982 +Name = "a grave stone" +Flags = {Unmove,AllowDistRead} + +TypeID = 1983 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1984 +Name = "a stone coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1985 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1986 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1987 +Name = "a stone coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1988 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1989 +Name = "a buried coffin" +Flags = {Unmove} + +TypeID = 1990 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2474} + +TypeID = 1991 +Name = "a wooden coffin" +Flags = {Bottom,Container,Unpass,Unmove,Height,Disguise} +Attributes = {Capacity=6,DisguiseTarget=2476} + +TypeID = 1992 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1993 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1994 +Name = "a sarcophagus" +Flags = {Bottom,Container,Unpass,Unmove,Height} +Attributes = {Capacity=6} + +TypeID = 1995 +Name = "a sarcophagus" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 1996 +Name = "a stone circle" +Flags = {Unmove} + +TypeID = 1997 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 1998 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=7,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 1999 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=5,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2000 +Name = "a campfire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,NoReplace} +Attributes = {Brightness=3,LightColor=206,AvoidDamageTypes=FIRE} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2001 +Name = "an unlit campfire" +Flags = {Unmove} + +TypeID = 2002 +Name = "a campfire" +Flags = {Unpass,Unmove} + +TypeID = 2003 +Name = "a campfire" +Flags = {Unpass,Unmove} +Attributes = {Brightness=5,LightColor=206} + +TypeID = 2004 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2005 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2006 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2007 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2008 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2009 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2010 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2011 +Name = "a large cauldron" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 2012 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2013 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2014 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2015 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2016 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2017 +Name = "a sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2018 +Name = "a dragon flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2019 +Name = "a castle flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2020 +Name = "a flag of Tibia" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2021 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2022 +Name = "a street sign" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2023 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2024 +Name = "a sign" +Flags = {Unmove,Hang,AllowDistRead} + +TypeID = 2025 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2059,DestroyTarget=3141} + +TypeID = 2026 +Name = "a statue" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2027 +Name = "a hero statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2028 +Name = "a monument" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2029 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2045,DestroyTarget=3142} + +TypeID = 2030 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2048,DestroyTarget=3142} + +TypeID = 2031 +Name = "an angel statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2032 +Name = "a dwarven statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2033 +Name = "a watchdog statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2034 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2035 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2036 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2037 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2038 +Name = "a gargoyle statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2039 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2040 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2041 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2042 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2043 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2044,DestroyTarget=3142} + +TypeID = 2044 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2029,DestroyTarget=3142} + +TypeID = 2045 +Name = "a minotaur statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2043,DestroyTarget=3142} + +TypeID = 2046 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2047,DestroyTarget=3142} + +TypeID = 2047 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2030,DestroyTarget=3142} + +TypeID = 2048 +Name = "a goblin statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2046,DestroyTarget=3142} + +TypeID = 2049 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2050 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2051 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2052 +Name = "a pedestal" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2053 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2054 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2055 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2056 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2057 +Name = "a cobra statue" +Flags = {Unmove,Hang} + +TypeID = 2058 +Name = "an ornament" +Flags = {Unmove,Hang} + +TypeID = 2059 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2060,DestroyTarget=3141} + +TypeID = 2060 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2061,DestroyTarget=3141} + +TypeID = 2061 +Name = "a statue" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2025,DestroyTarget=3141} + +TypeID = 2062 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2063} + +TypeID = 2063 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2062,Brightness=6,LightColor=206} + +TypeID = 2064 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2065} + +TypeID = 2065 +Name = "a sacral statue" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2064,Brightness=6,LightColor=206} + +TypeID = 2066 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2067 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2068 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2069 +Name = "a broken lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2070 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2071 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2072 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2073 +Name = "a lizard statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2074 +Name = "a small pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2075 +Name = "a small lit pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=207} + +TypeID = 2076 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2077 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2078 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2079 +Name = "a snake wall" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2080 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2081 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2082 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2083 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2084 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2085 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2086 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2087 +Name = "a giant lizard head" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2088 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2089 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2090 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2091 +Name = "a giant lizard claw" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2092 +Name = "a stone snake wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2093 +Name = "a stone snake pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2094 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2095 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2096 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2097 +Name = "a dried well" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2098 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2099 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2100 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2101 +Name = "a poison well" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=SLIME,Brightness=2,LightColor=104} + +TypeID = 2102 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2103 +Name = "a pagoda" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2104 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2105 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2106 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2107 +Name = "a huntress statue" +Flags = {Bottom,Unpass,Unmove,Unlay,AllowDistRead} + +TypeID = 2108 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2109,Brightness=0,LightColor=215} + +TypeID = 2109 +Name = "a street lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2108,Brightness=7,LightColor=207} + +TypeID = 2110 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=207} + +TypeID = 2111 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2112 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2113 +Name = "a coal basin" +Flags = {Unpass,Unmove,Height} +Attributes = {Brightness=8,LightColor=206} + +TypeID = 2114 +Name = "an empty coal basin" +Flags = {CollisionEvent,Unpass,Unmove,Height} +Attributes = {Brightness=0,LightColor=215} + +TypeID = 2115 +Name = "a stone coal basin" +Flags = {Unpass,Unmove,Unlay,Height} +Attributes = {Brightness=7,LightColor=206} + +TypeID = 2116 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2117,Brightness=0,LightColor=215} + +TypeID = 2117 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 2118 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200,ExpireTarget=2119,TotalExpireTime=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2119 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2120,TotalExpireTime=150} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2120 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=206,ExpireTarget=0,TotalExpireTime=100} + +TypeID = 2121 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104,ExpireTarget=0,TotalExpireTime=250} +MagicField = {Type=POISON,Count=100} + +TypeID = 2122 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137,ExpireTarget=0,TotalExpireTime=100} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2123 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=200} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2124 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2125 +Name = "a fire" +Flags = {Unmove,MagicField} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 2126 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=137} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2127 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=104} +MagicField = {Type=POISON,Count=100} + +TypeID = 2128 +Name = "a magic wall" +Flags = {Unpass,CollisionEvent,Unmove,Unthrow,Unlay,MagicField,Expire} +Attributes = {Brightness=3,LightColor=5,ExpireTarget=0,TotalExpireTime=20} + +TypeID = 2129 +Name = "a magic wall" +Flags = {Unpass,Unmove,Unthrow,Unlay,MagicField} +Attributes = {Brightness=3,LightColor=5} + +TypeID = 2130 +Name = "rush wood" +Flags = {Unpass,Unmove,Unlay,MagicField,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=45} + +TypeID = 2131 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=7,LightColor=206,ExpireTarget=2132,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=70,Damage=20} + +TypeID = 2132 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=FIRE,Brightness=5,LightColor=206,ExpireTarget=2133,TotalExpireTime=5} +MagicField = {Type=FIRE,Count=50,Damage=10} + +TypeID = 2133 +Name = "a fire" +Flags = {Unmove,MagicField,Expire} +Attributes = {Brightness=3,LightColor=207,ExpireTarget=0,TotalExpireTime=5} + +TypeID = 2134 +Name = "poison gas" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=POISON,Brightness=2,LightColor=214,ExpireTarget=0,TotalExpireTime=8} +MagicField = {Type=POISON,Count=100} + +TypeID = 2135 +Name = "an energy field" +Flags = {CollisionEvent,Unmove,Avoid,MagicField,Expire} +Attributes = {AvoidDamageTypes=ENERGY,Brightness=4,LightColor=214,ExpireTarget=0,TotalExpireTime=5} +MagicField = {Type=ENERGY,Count=25,Damage=30} + +TypeID = 2136 +Name = "smoke" +Flags = {Unmove,MagicField} + +TypeID = 2137 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2138,TotalExpireTime=7} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2138 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=203,ExpireTarget=2151,TotalExpireTime=2} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2139 +Name = "a fire" +Flags = {Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2140,TotalExpireTime=2,DisguiseTarget=2140} + +TypeID = 2140 +Name = "ashes" +Flags = {Unmove,Avoid,Expire} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2137,TotalExpireTime=8} + +TypeID = 2141 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2142 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=203,ExpireTarget=2140,TotalExpireTime=7,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2143 +Name = "ashes" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,NoReplace} +Attributes = {AvoidDamageTypes=PHYSICAL,ExpireTarget=2139,TotalExpireTime=1,DisguiseTarget=2140} + +TypeID = 2144 +Name = "lava" +Flags = {Bank,Unmove,Avoid} +Attributes = {Waypoints=150,Brightness=4,LightColor=193} + +TypeID = 2145 +Name = "strange slits" +Flags = {CollisionEvent,Unmove} + +TypeID = 2146 +Name = "blades" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2145,TotalExpireTime=3} + +TypeID = 2147 +Name = "strange holes" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2148,TotalExpireTime=1} + +TypeID = 2148 +Name = "spikes" +Flags = {CollisionEvent,Unmove,Expire} +Attributes = {ExpireTarget=2147,TotalExpireTime=3} + +TypeID = 2149 +Name = "a searing fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=7,LightColor=209,ExpireTarget=2150,TotalExpireTime=5,DisguiseTarget=2137} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2150 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=5,LightColor=209,ExpireTarget=2149,TotalExpireTime=1,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2151 +Name = "a fire" +Flags = {CollisionEvent,Unmove,Avoid,Expire,Disguise,MagicField} +Attributes = {AvoidDamageTypes=PHYSICAL,Brightness=2,LightColor=209,ExpireTarget=2139,TotalExpireTime=2,DisguiseTarget=2138} +MagicField = {Type=FIRE,Count=10,Damage=300} + +TypeID = 2152 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2153 +Name = "a marble pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2154 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2155 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2156 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2157 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2158 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2159 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2160 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2161 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2162 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2163 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2164 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2165 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2166 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2167 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2168 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2169 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2170 +Name = "a framework wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2171 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2172 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2173 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2174 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2175 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2176 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2177 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2178} + +TypeID = 2178 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2177} + +TypeID = 2179 +Name = "a closed fence gate" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2180} + +TypeID = 2180 +Name = "an open fence gate" +Flags = {Top,ChangeUse,Unmove} +Attributes = {ChangeTarget=2179} + +TypeID = 2181 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2182 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2183 +Name = "a fence" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2184 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2185 +Name = "bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2186 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2186} + +TypeID = 2187 +Name = "nothing special" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2188 +Name = "a sandstone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2189 +Name = "a sandstone statue" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2190 +Name = "an oriental pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2191 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2192 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2193 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2194 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2195 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2196 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2197 +Name = "a ramp" +Flags = {Bottom,Unmove,Height} + +TypeID = 2198 +Name = "a ramp" +Flags = {CollisionEvent,Bottom,Unmove,Avoid,Height} + +TypeID = 2199 +Name = "an obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2200 +Name = "a broken obelisk" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2201 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2202 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2203 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2204 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2205 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2206 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2207 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2208 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2209 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2210 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2211 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2212 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2213 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2214 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2215 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2216 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2217 +Name = "an ominous pillar" +Flags = {Bottom,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2190} + +TypeID = 2218 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2219 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2220 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2221 +Name = "a ramp" +Flags = {Bottom,Unpass,Unmove,Unlay,Height} + +TypeID = 2222 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2223 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2224 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2225 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2226 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2227 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2228 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2229 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2230 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2231 +Name = "a sandstone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2232 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2233 +Name = "a stone railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2234 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2235 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2236 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2237 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2238 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2239 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2240 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2241 +Name = "a wooden railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2242 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2243 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2244 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2245 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2246 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2247 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2248 +Name = "a bamboo column" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2249 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2250 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2251 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2252 +Name = "a bamboo pole" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2253 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2254 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2255 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2256 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2257 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2258 +Name = "a short pillar" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2259 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2260 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2261 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2262 +Name = "a rope bridge" +Flags = {Bank,Unmove} +Attributes = {Waypoints=200} + +TypeID = 2263 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2264 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2265 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2266 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2267 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2268 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2269 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2270 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2271 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2272 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2273 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2274 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2275 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2276 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2277 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2278 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2279 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2280 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2281 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2282 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2283 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2284 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2285 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2286 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2287 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2288 +Name = "a rope railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2289 +Name = "a stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2290 +Name = "a broken stone pillar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2291 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2292 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2293 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2294 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2295 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3146} + +TypeID = 2296 +Name = "wooden bars" +Description = "They already have some cracks and look rather fragile" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=3145} + +TypeID = 2297 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2298 +Name = "wooden bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2299 +Name = "a small totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2300 +Name = "a large totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2301 +Name = "a totem pole" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 2302 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2303 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2304 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2305 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2306 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2307 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2308 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2309 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2310 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2311 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2312 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2313 +Name = "a big table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2314 +Name = "a big table" +Flags = {Destroy,Unpass,Height} +Attributes = {DestroyTarget=3138} + +TypeID = 2315 +Name = "a square table" +Flags = {Destroy,Unpass,Height} +Attributes = {DestroyTarget=3138} + +TypeID = 2316 +Name = "a small round table" +Flags = {Destroy,Unpass,Height} +Attributes = {DestroyTarget=3138} + +TypeID = 2317 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2318 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2319 +Name = "a small table" +Flags = {Destroy,Unpass,Height} +Attributes = {DestroyTarget=3140} + +TypeID = 2320 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2321 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2322 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2323 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2324 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2325 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2326 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2327 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2328 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2329 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2330 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2331 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2332 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2333 +Name = "a table" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2334 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2335 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2336 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2337 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2338 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2339 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2340 +Name = "a passthrough" +Flags = {Bottom,Door,Unpass,Unmove,Height} + +TypeID = 2341 +Name = "an open passthrough" +Flags = {Bottom,Door,Unmove} + +TypeID = 2342 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2343 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2344 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2345 +Name = "a counter" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2346 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2347,DestroyTarget=3141} + +TypeID = 2347 +Name = "a carved stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2348 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2349,DestroyTarget=3137} + +TypeID = 2349 +Name = "a tusk table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2348,DestroyTarget=3137} + +TypeID = 2350 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2351,DestroyTarget=3137} + +TypeID = 2351 +Name = "a bamboo table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2350,DestroyTarget=3137} + +TypeID = 2352 +Name = "a thick trunk" +Flags = {Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2353 +Name = "an ornamented stone table" +Flags = {Rotate,Destroy,Height} +Attributes = {RotateTarget=2346,DestroyTarget=3141} + +TypeID = 2354 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2355 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2356 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2357 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2358 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2359,DestroyTarget=3138} + +TypeID = 2359 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2360,DestroyTarget=3138} + +TypeID = 2360 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2361,DestroyTarget=3138} + +TypeID = 2361 +Name = "a wooden chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2358,DestroyTarget=3138} + +TypeID = 2362 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2363 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2364 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2365 +Name = "a throne" +Flags = {Unmove,Avoid,Height} + +TypeID = 2366 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2369,DestroyTarget=3139} + +TypeID = 2367 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2368,DestroyTarget=3139} + +TypeID = 2368 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2366,DestroyTarget=3139} + +TypeID = 2369 +Name = "a sofa chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2367,DestroyTarget=3139} + +TypeID = 2370 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2371 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2372 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2373 +Name = "a bench" +Flags = {Unmove,Avoid,Height} + +TypeID = 2374 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2377,DestroyTarget=3138} + +TypeID = 2375 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2376,DestroyTarget=3138} + +TypeID = 2376 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2374,DestroyTarget=3138} + +TypeID = 2377 +Name = "a red cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2375,DestroyTarget=3138} + +TypeID = 2378 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2381,DestroyTarget=3138} + +TypeID = 2379 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2380,DestroyTarget=3138} + +TypeID = 2380 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2378,DestroyTarget=3138} + +TypeID = 2381 +Name = "a green cushioned chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2379,DestroyTarget=3138} + +TypeID = 2382 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2385,DestroyTarget=3138} + +TypeID = 2383 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2384,DestroyTarget=3138} + +TypeID = 2384 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2382,DestroyTarget=3138} + +TypeID = 2385 +Name = "a rocking chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2383,DestroyTarget=3138} + +TypeID = 2386 +Name = "a small purple pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2387 +Name = "a small green pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2388 +Name = "a small red pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2389 +Name = "a small blue pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2390 +Name = "a small orange pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2391 +Name = "a small turquoise pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2392 +Name = "a small white pillow" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 2393 +Name = "a heart pillow" +Flags = {Take} +Attributes = {Weight=1700} + +TypeID = 2394 +Name = "a blue pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2395 +Name = "a red pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2396 +Name = "a green pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2397 +Name = "a yellow pillow" +Flags = {Take} +Attributes = {Weight=1600} + +TypeID = 2398 +Name = "a round blue pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2399 +Name = "a round red pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2400 +Name = "a round purple pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2401 +Name = "a round turquoise pillow" +Flags = {Take} +Attributes = {Weight=1550} + +TypeID = 2402 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2403 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2404 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2405 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2406 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2407 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2408 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2409 +Name = "a couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2410 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2411 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2412 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2413 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2414 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2415 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2416 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2417 +Name = "a luxurious couch" +Flags = {Unmove,Avoid,Height} + +TypeID = 2418 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2421,DestroyTarget=3136} + +TypeID = 2419 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2420,DestroyTarget=3136} + +TypeID = 2420 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2418,DestroyTarget=3136} + +TypeID = 2421 +Name = "a tusk chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2419,DestroyTarget=3136} + +TypeID = 2422 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2425,DestroyTarget=3136} + +TypeID = 2423 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2424,DestroyTarget=3136} + +TypeID = 2424 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2422,DestroyTarget=3136} + +TypeID = 2425 +Name = "an ivory chair" +Flags = {Avoid,Rotate,Destroy,Height} +Attributes = {RotateTarget=2423,DestroyTarget=3136} + +TypeID = 2426 +Name = "a small trunk" +Flags = {Avoid,Destroy,Height} +Attributes = {DestroyTarget=3136} + +TypeID = 2427 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2428 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2429 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2430 +Name = "a wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2431 +Name = "drawers" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2434,DestroyTarget=3136} + +TypeID = 2432 +Name = "drawers" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2431,DestroyTarget=3136} + +TypeID = 2433 +Name = "drawers" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2432,DestroyTarget=3136} + +TypeID = 2434 +Name = "drawers" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2433,DestroyTarget=3136} + +TypeID = 2435 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2436 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2437 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2438 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2439 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2440 +Name = "a bookcase" +Flags = {Container,Unpass,Unmove,Unlay,Height} +Attributes = {Capacity=6} + +TypeID = 2441 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2444,DestroyTarget=3139} + +TypeID = 2442 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2441,DestroyTarget=3139} + +TypeID = 2443 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2442,DestroyTarget=3139} + +TypeID = 2444 +Name = "a dresser" +Flags = {Container,Rotate,Unpass,Destroy,Height} +Attributes = {Capacity=10,RotateTarget=2443,DestroyTarget=3139} + +TypeID = 2445 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2448,DestroyTarget=3139} + +TypeID = 2446 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2445,DestroyTarget=3139} + +TypeID = 2447 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2446,DestroyTarget=3139} + +TypeID = 2448 +Name = "a pendulum clock" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2447,DestroyTarget=3139} + +TypeID = 2449 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2452,DestroyTarget=3140} + +TypeID = 2450 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2451,DestroyTarget=3140} + +TypeID = 2451 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2449,DestroyTarget=3140} + +TypeID = 2452 +Name = "a locker" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2450,DestroyTarget=3140} + +TypeID = 2453 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2454,DestroyTarget=3140} + +TypeID = 2454 +Name = "a standing mirror" +Description = "You look fine today" +Flags = {Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2453,DestroyTarget=3140} + +TypeID = 2455 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2456 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2457 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2458 +Name = "a bamboo wardrobe" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2459 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2460 +Name = "a bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2461 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2462 +Name = "a small bamboo shelf" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 2463 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2464 +Name = "a small bamboo shelf" +Flags = {Container,Unpass,Unmove,Unlay} +Attributes = {Capacity=6} + +TypeID = 2465 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2468,DestroyTarget=3136} + +TypeID = 2466 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2465,DestroyTarget=3136} + +TypeID = 2467 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2466,DestroyTarget=3136} + +TypeID = 2468 +Name = "a bamboo drawer" +Flags = {Container,Unpass,Unlay,Rotate,Destroy} +Attributes = {Capacity=6,RotateTarget=2467,DestroyTarget=3136} + +TypeID = 2469 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3135} + +TypeID = 2470 +Name = "a box" +Flags = {Container,Unmove,Avoid,Height,Disguise} +Attributes = {Capacity=10,DisguiseTarget=2469} + +TypeID = 2471 +Name = "a crate" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=15,Weight=8000,DestroyTarget=3135} + +TypeID = 2472 +Name = "a chest" +Flags = {Container,Unpass,Take,Rotate,Destroy,Height} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2482,DestroyTarget=3137} + +TypeID = 2473 +Name = "a box" +Flags = {Container,Avoid,Take,Destroy,Height} +Attributes = {Capacity=10,Weight=3500,DestroyTarget=3140} + +TypeID = 2474 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2475 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2476 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2477 +Name = "a wooden coffin" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2478 +Name = "a treasure chest" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=9500} + +TypeID = 2479 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2480 +Name = "a chest" +Flags = {Container,Unpass,Take,Rotate,Destroy,Height} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2481,DestroyTarget=3137} + +TypeID = 2481 +Name = "a chest" +Flags = {Container,Unpass,Take,Rotate,Destroy,Height} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2472,DestroyTarget=3137} + +TypeID = 2482 +Name = "a chest" +Flags = {Container,Unpass,Take,Rotate,Destroy,Height} +Attributes = {Capacity=15,Weight=12000,RotateTarget=2480,DestroyTarget=3137} + +TypeID = 2483 +Name = "a large trunk" +Flags = {Container,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2486,DestroyTarget=3140} + +TypeID = 2484 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2485,DestroyTarget=3140} + +TypeID = 2485 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2483,DestroyTarget=3140} + +TypeID = 2486 +Name = "a large trunk" +Flags = {Container,Unpass,Rotate,Destroy,Height} +Attributes = {Capacity=18,RotateTarget=2484,DestroyTarget=3140} + +TypeID = 2487 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2495} + +TypeID = 2488 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2496} + +TypeID = 2489 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2501} + +TypeID = 2490 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2502} + +TypeID = 2491 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2499} + +TypeID = 2492 +Name = "a cot" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2500} + +TypeID = 2493 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2497} + +TypeID = 2494 +Name = "a bed" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2498} + +TypeID = 2495 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2487} + +TypeID = 2496 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2488} + +TypeID = 2497 +Name = "a bed" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2493} + +TypeID = 2498 +Name = "a bed" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2494} + +TypeID = 2499 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2491} + +TypeID = 2500 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2492} + +TypeID = 2501 +Name = "a cot" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2489} + +TypeID = 2502 +Name = "a cot" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2490} + +TypeID = 2503 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedTarget=2507} + +TypeID = 2504 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedTarget=2508} + +TypeID = 2505 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedTarget=2509} + +TypeID = 2506 +Name = "a hammock" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedTarget=2510} + +TypeID = 2507 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=SOUTH,BedFree=2503} + +TypeID = 2508 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=NORTH,BedFree=2504} + +TypeID = 2509 +Name = "a hammock" +Flags = {UseEvent,Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=EAST,BedFree=2505} + +TypeID = 2510 +Name = "a hammock" +Description = "Somebody is sleeping there" +Flags = {Bed,Unpass,Unmove,Unlay,Height} +Attributes = {BedDirection=WEST,BedFree=2506} + +TypeID = 2511 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2512 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2513 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2514 +Name = "a grass mat" +Flags = {Unmove} + +TypeID = 2515 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2516 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2517 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2518 +Name = "a straw mat" +Flags = {Unmove} + +TypeID = 2519 +Name = "a barrel" +Flags = {Container,Unpass,Destroy,Height} +Attributes = {Capacity=25,DestroyTarget=3138} + +TypeID = 2520 +Name = "a water cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 2521 +Name = "a lemonade cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=LEMONADE} + +TypeID = 2522 +Name = "a wine cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2523 +Name = "a barrel" +Flags = {Container,Unpass,Destroy,Height} +Attributes = {Capacity=25,DestroyTarget=3135} + +TypeID = 2524 +Name = "a trough" +Flags = {MultiUse,FluidContainer,Unpass,Destroy,Height} +Attributes = {DestroyTarget=3135} + +TypeID = 2525 +Name = "a beer cask" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BEER} + +TypeID = 2526 +Name = "a dustbin" +Flags = {CollisionEvent,Unpass,Unmove} + +TypeID = 2527 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2528 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2529 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2530 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2531 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2532 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2533 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2534 +Name = "a big wine cask" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WINE} + +TypeID = 2535 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2536,Brightness=3,LightColor=199} + +TypeID = 2536 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2535,Brightness=0,LightColor=215} + +TypeID = 2537 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2538,Brightness=3,LightColor=193} + +TypeID = 2538 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2537,Brightness=0,LightColor=215} + +TypeID = 2539 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2540,Brightness=3,LightColor=193} + +TypeID = 2540 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2539,Brightness=0,LightColor=215} + +TypeID = 2541 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2542,Brightness=3,LightColor=193} + +TypeID = 2542 +Name = "an oven" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Height} +Attributes = {ChangeTarget=2541,Brightness=0,LightColor=215} + +TypeID = 2543 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2469} + +TypeID = 2544 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2476} + +TypeID = 2545 +Name = "a wooden coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2474} + +TypeID = 2546 +Name = "a chest" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2472} + +TypeID = 2547 +Name = "a bananapalm" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3639} + +TypeID = 2548 +Name = "a dead dragon" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4025} + +TypeID = 2549 +Name = "a honeyflower patch" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2984} + +TypeID = 2550 +Name = "a dead human" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4240} + +TypeID = 2551 +Name = "a box" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {DisguiseTarget=2473} + +TypeID = 2552 +Name = "a dead tree" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3634} + +TypeID = 2553 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2433} + +TypeID = 2554 +Name = "drawers" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2434} + +TypeID = 2555 +Name = "a small hole" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=130,DisguiseTarget=387} + +TypeID = 2556 +Name = "a loose board" +Flags = {Bank,Chest,Unmove,Avoid,Disguise} +Attributes = {Waypoints=100,DisguiseTarget=408} + +TypeID = 2557 +Name = "a pile of bones" +Flags = {Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=4285} + +TypeID = 2558 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=2435} + +TypeID = 2559 +Name = "a bookcase" +Flags = {Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=2438} + +TypeID = 2560 +Name = "a stone coffin" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1983} + +TypeID = 2561 +Name = "a barrel" +Flags = {Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=2523} + +TypeID = 2562 +Name = "a hollow stone" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Height,Disguise} +Attributes = {,DisguiseTarget=1777} + +TypeID = 2563 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=4305} + +TypeID = 2564 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1994} + +TypeID = 2565 +Name = "a sarcophagus" +Flags = {Bottom,Chest,Unpass,Unmove,Height,Disguise} +Attributes = {,DisguiseTarget=1992} + +TypeID = 2566 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2567 +Name = "a lever" +Description = "It doesn't move" +Flags = {UseEvent,Unmove,Expire,Disguise} +Attributes = {ExpireTarget=2566,TotalExpireTime=240,DisguiseTarget=2773} + +TypeID = 2568 +Name = "a cobra statue" +Flags = {Bottom,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=2054} + +TypeID = 2569 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2772} + +TypeID = 2570 +Name = "a lever" +Flags = {UseEvent,Unmove,Disguise} +Attributes = {DisguiseTarget=2773} + +TypeID = 2571 +Name = "a stone" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire,Disguise} +Attributes = {ExpireTarget=0,TotalExpireTime=300,DisguiseTarget=1772} + +TypeID = 2572 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2573 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2574 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2575 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2576 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2577 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2578 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2579 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2580 +Name = "a red carpet" +Flags = {Clip,Unmove} + +TypeID = 2581 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2582 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2583 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2584 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2585 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2586 +Name = "an oriental carpet" +Flags = {Clip,Unmove} + +TypeID = 2587 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2588 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2589 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2590 +Name = "a brown bear fur" +Flags = {Clip,Unmove} + +TypeID = 2591 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2592 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2593 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2594 +Name = "a polar bear fur" +Flags = {Clip,Unmove} + +TypeID = 2595 +Name = "a badger fur" +Flags = {Clip,Unmove} + +TypeID = 2596 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2597 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2598 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2599 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2600 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2601 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2602 +Name = "a blackboard" +Flags = {Unmove,AllowDistRead} + +TypeID = 2603 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2604 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2605 +Name = "a blackboard" +Flags = {Text,Write,Unmove,AllowDistRead} +Attributes = {MaxLength=200} + +TypeID = 2606 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2607 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2608 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2609 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2610 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2611 +Name = "an emblem" +Flags = {Unmove} + +TypeID = 2612 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2613 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2614 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2615 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2616 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2617 +Name = "a scarab ornament" +Flags = {UseEvent,Unmove} + +TypeID = 2618 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2619 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2620 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2621 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2622 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2623 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2624 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2625 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2626 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2627 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2628 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2629 +Name = "a painting" +Flags = {Unmove} + +TypeID = 2630 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2631 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2632 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2633 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2634 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2635 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2636 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2637 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Unmove} + +TypeID = 2638 +Name = "a wall mirror" +Description = "You look fine today" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2639 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2640 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2641 +Name = "a picture" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2642 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2643 +Name = "a purple tapestry" +Flags = {Unmove} + +TypeID = 2644 +Name = "a purple tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2645 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2646 +Name = "a green tapestry" +Flags = {Unmove} + +TypeID = 2647 +Name = "a green tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2648 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2649 +Name = "a yellow tapestry" +Flags = {Unmove} + +TypeID = 2650 +Name = "a yellow tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2651 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2652 +Name = "an orange tapestry" +Flags = {Unmove} + +TypeID = 2653 +Name = "an orange tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2654 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2655 +Name = "a red tapestry" +Flags = {Unmove} + +TypeID = 2656 +Name = "a red tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2657 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2658 +Name = "a blue tapestry" +Flags = {Unmove} + +TypeID = 2659 +Name = "a blue tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2660 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2661,TotalExpireTime=595} + +TypeID = 2661 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2660,TotalExpireTime=5} + +TypeID = 2662 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=595} + +TypeID = 2663 +Name = "a cuckoo clock" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2663,TotalExpireTime=5} + +TypeID = 2664 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2668,TotalExpireTime=600} + +TypeID = 2665 +Name = "a white tapestry" +Flags = {Unmove} + +TypeID = 2666 +Name = "a tapestry" +Flags = {Unmove} + +TypeID = 2667 +Name = "a white tapestry" +Flags = {Take,Hang} +Attributes = {Weight=1000} + +TypeID = 2668 +Name = "a cuckoo clock" +Flags = {Take,Hang,Expire} +Attributes = {Weight=800,ExpireTarget=2664,TotalExpireTime=5} + +TypeID = 2669 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2670 +Name = "a demon trophy" +Flags = {Unmove} + +TypeID = 2671 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2672 +Name = "a wolf trophy" +Flags = {Unmove} + +TypeID = 2673 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2674 +Name = "an orc trophy" +Flags = {Unmove} + +TypeID = 2675 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2676 +Name = "a behemoth trophy" +Flags = {Unmove} + +TypeID = 2677 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2678 +Name = "a deer trophy" +Flags = {Unmove} + +TypeID = 2679 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2680 +Name = "a cyclops trophy" +Flags = {Unmove} + +TypeID = 2681 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2682 +Name = "a dragon lord trophy" +Flags = {Unmove} + +TypeID = 2683 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2684 +Name = "a lion trophy" +Flags = {Unmove} + +TypeID = 2685 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2686 +Name = "a minotaur trophy" +Flags = {Unmove} + +TypeID = 2687 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2688 +Name = "a feather decoration" +Flags = {Unmove} + +TypeID = 2689 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2690 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2691 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2692 +Name = "a dried fur" +Flags = {Unmove} + +TypeID = 2693 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2694 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2695 +Name = "a bloodspot" +Flags = {Unmove,Hang} + +TypeID = 2696 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2697 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2698 +Name = "a bloodspot" +Flags = {Unmove} + +TypeID = 2699 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2700 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2701 +Name = "cobwebs" +Flags = {Unmove,Hang} + +TypeID = 2702 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2703 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2704 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2705 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2706 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2707 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2708 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2709 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2710 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2711 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2712 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2713 +Name = "a flowery wall" +Flags = {Unmove,Hang} + +TypeID = 2714 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2715 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2716 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2717 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2718 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2719 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2720 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2721 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2722 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2723 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2724 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2725 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2726 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2727 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2728 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2729 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2730 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2731 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2732 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2733 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2734 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2735 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2736 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2737 +Name = "a mossy wall" +Flags = {Unmove,Hang} + +TypeID = 2738 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2739 +Name = "tanned brown bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2740 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2741 +Name = "tanned polar bear fur" +Flags = {Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2742 +Name = "a pile of chopped wood" +Flags = {Unpass,Unmove} + +TypeID = 2743 +Name = "a block of wood" +Description = "It's a lumberjack's working place" +Flags = {Unpass,Unmove} + +TypeID = 2744 +Name = "some pieces of wood" +Flags = {Unmove} + +TypeID = 2745 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2746 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2747 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2748 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2749 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2750 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2751 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2752 +Name = "a fishing net" +Flags = {Unmove,Hang} + +TypeID = 2753 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2754 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2755 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2756 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2757 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2758 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2759 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2760 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2761 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2762 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2763 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2764 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2765 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2766 +Name = "a giant footprint" +Flags = {Bottom,Unmove} + +TypeID = 2767 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2768 +Name = "tanned tiger fur" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 2769 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2770 +Name = "a tiger fur" +Flags = {Unmove,Hang} + +TypeID = 2771 +Name = "a sundial" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 2772 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2773 +Name = "a lever" +Flags = {UseEvent,Unmove} + +TypeID = 2774 +Name = "a torch bearer" +Flags = {UseEvent,Unmove,Hang,Disguise} +Attributes = {Brightness=0,LightColor=215,DisguiseTarget=2928} + +TypeID = 2775 +Name = "a furniture package" +Description = "It contains a construction kit for a red cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2776 +Name = "a furniture package" +Description = "It contains a construction kit for a green cushioned chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2777 +Name = "a furniture package" +Description = "It contains a construction kit for a wooden chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2778 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2779 +Name = "a furniture package" +Description = "It contains a construction kit for a sofa chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2780 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2781 +Name = "a furniture package" +Description = "It contains a construction kit for a ivory chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2782 +Name = "a furniture package" +Description = "It contains a construction kit for a small table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2783 +Name = "a furniture package" +Description = "It contains a construction kit for a round table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2784 +Name = "a furniture package" +Description = "It contains a construction kit for a square table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2785 +Name = "a furniture package" +Description = "It contains a construction kit for a big table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2786 +Name = "a furniture package" +Description = "It contains a construction kit for a stone table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2787 +Name = "a furniture package" +Description = "It contains a construction kit for a tusk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2788 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2789 +Name = "a furniture package" +Description = "It contains a construction kit for a drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2790 +Name = "a furniture package" +Description = "It contains a construction kit for a dresser" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2791 +Name = "a furniture package" +Description = "It contains a construction kit for a locker" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2792 +Name = "a furniture package" +Description = "It contains a construction kit for a trough" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2793 +Name = "a furniture package" +Description = "It contains a construction kit for a barrel" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2794 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2795 +Name = "a furniture package" +Description = "It contains a construction kit for a bamboo drawer" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2796 +Name = "a furniture package" +Description = "It contains a construction kit for a birdcage" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2797 +Name = "a furniture package" +Description = "It contains a construction kit for a globe" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2798 +Name = "a furniture package" +Description = "It contains a construction kit for a table lamp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2000} + +TypeID = 2799 +Name = "a furniture package" +Description = "It contains a construction kit for a telescope" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2800 +Name = "a furniture package" +Description = "It contains a construction kit for a rocking horse" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2801 +Name = "a furniture package" +Description = "It contains a construction kit for a pendulum clock" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2802 +Name = "a furniture package" +Description = "It contains a construction kit for a knight statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2803 +Name = "a furniture package" +Description = "It contains a construction kit for a minotaur statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2804 +Name = "a furniture package" +Description = "It contains a construction kit for a goblin statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 2805 +Name = "a furniture package" +Description = "It contains a construction kit for a large amphora" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2806 +Name = "a furniture package" +Description = "It contains a construction kit for a coal basin" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2807 +Name = "a furniture package" +Description = "It contains a construction kit for a piano" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2808 +Name = "a furniture package" +Description = "It contains a construction kit for a harp" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2809 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk chair" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2810 +Name = "a furniture package" +Description = "It contains a construction kit for a trunk table" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3500} + +TypeID = 2811 +Name = "a furniture package" +Description = "It contains an indoor plant" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=3000} + +TypeID = 2812 +Name = "a furniture package" +Description = "It contains a christmas tree" +Flags = {UseEvent,Avoid,Take,Expire,Height} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2813 +Name = "a blank paper" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2814 +Name = "a parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2815 +Name = "a scroll" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=512,Weight=50} + +TypeID = 2816 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2817 +Name = "a blank parchment" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=200} + +TypeID = 2818 +Name = "a document" +Description = "It is rewriteable" +Flags = {Text,Write,Take} +Attributes = {MaxLength=1024,Weight=150} + +TypeID = 2819 +Name = "a parchment" +Flags = {Text,Take} +Attributes = {Weight=200} + +TypeID = 2820 +Name = "a paper" +Flags = {Text,Take} +Attributes = {Weight=100} + +TypeID = 2821 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2822 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=830} + +TypeID = 2823 +Name = "a map" +Flags = {Text,Take} +Attributes = {Weight=790} + +TypeID = 2824 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2825 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2826 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2827 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2828 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2829 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2830 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2831 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2832 +Name = "a book" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2833 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2834 +Name = "a document" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=150} + +TypeID = 2835 +Name = "a parchment" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=200} + +TypeID = 2836 +Name = "the holy Tible" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 2837 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2838 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2839 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2840 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2841 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2842 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2843 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2844 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2845 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2846 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2847 +Name = "a book" +Flags = {Text,WriteOnce,Take} +Attributes = {maxLength=1024,Weight=1300} + +TypeID = 2848 +Name = "a purple tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2849 +Name = "a green tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2850 +Name = "a blue tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2851 +Name = "a grey tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2852 +Name = "a red tome" +Description = "It's a volume of The Mystic Secrets of Tibia" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 2853 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2854 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2855 +Name = "a basket" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=950} + +TypeID = 2856 +Name = "a present" +Flags = {Container,Take} +Attributes = {Capacity=5,Weight=600} + +TypeID = 2857 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2858 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2859 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2860 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2861 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2862 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2863 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2864 +Name = "a bag" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=800,SlotType=BACKPACK} + +TypeID = 2865 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2866 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2867 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2868 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2869 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2870 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2871 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2872 +Name = "a backpack" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 2873 +Name = "a bucket" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=2000} + +TypeID = 2874 +Name = "a vial" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=180} + +TypeID = 2875 +Name = "a bottle" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2876 +Name = "a vase" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2877 +Name = "a green flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2878 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2879 +Name = "an elven vase" +Description = "It is made of very fine glass and covered with decorations" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=940} + +TypeID = 2880 +Name = "a mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=250} + +TypeID = 2881 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2882 +Name = "a jug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=750} + +TypeID = 2883 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2884 +Name = "a cup" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=200} + +TypeID = 2885 +Name = "a brown flask" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=300} + +TypeID = 2886 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2887,TotalExpireTime=120} + +TypeID = 2887 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2888,TotalExpireTime=120} + +TypeID = 2888 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2889 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2890,TotalExpireTime=120} + +TypeID = 2890 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=2891,TotalExpireTime=120} + +TypeID = 2891 +Name = "a pool" +Flags = {Bottom,Splash,Unmove,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=120} + +TypeID = 2892 +Name = "a broken bottle" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 2893 +Name = "an amphora" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=9700} + +TypeID = 2894 +Name = "a broken flask" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 2895 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2896 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2897 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2898 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2899 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2900 +Name = "a pool" +Flags = {Bottom,Splash,Unmove} + +TypeID = 2901 +Name = "a waterskin" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=700} + +TypeID = 2902 +Name = "a bowl" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=380} + +TypeID = 2903 +Name = "a golden mug" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=470} + +TypeID = 2904 +Name = "a large amphora" +Flags = {MultiUse,FluidContainer,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2905 +Name = "a plate" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 2906 +Name = "a watch" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 2907 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2908,Brightness=0,LightColor=0} + +TypeID = 2908 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2907,Brightness=6,LightColor=206} + +TypeID = 2909 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2910,Brightness=0,LightColor=0} + +TypeID = 2910 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2909,Brightness=6,LightColor=206} + +TypeID = 2911 +Name = "a candelabrum" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2912,Weight=5000,Brightness=0,LightColor=0} + +TypeID = 2912 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206,ExpireTarget=2913,TotalExpireTime=3000} + +TypeID = 2913 +Name = "a used candelabrum" +Flags = {Take} +Attributes = {Weight=4500,Brightness=0,LightColor=0} + +TypeID = 2914 +Name = "a lamp" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2915,Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2915 +Name = "a lit lamp" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2914,Weight=3000,Brightness=6,LightColor=199,ExpireTarget=2916,TotalExpireTime=2000} + +TypeID = 2916 +Name = "a used lamp" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3000,Brightness=0,LightColor=0} + +TypeID = 2917 +Name = "a candlestick" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2918,Weight=300,Brightness=0,LightColor=0} + +TypeID = 2918 +Name = "a lit candlestick" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2917,Weight=300,Brightness=4,LightColor=206,ExpireTarget=2919,TotalExpireTime=3000} + +TypeID = 2919 +Name = "a used candlestick" +Flags = {Take} +Attributes = {Weight=250,Brightness=0,LightColor=0} + +TypeID = 2920 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2921,Weight=500,Brightness=0,LightColor=215} + +TypeID = 2921 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2920,Weight=500,Brightness=7,LightColor=206,ExpireTarget=2923,TotalExpireTime=600} + +TypeID = 2922 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2923,Weight=450,Brightness=0,LightColor=215} + +TypeID = 2923 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2922,Weight=450,Brightness=6,LightColor=206,ExpireTarget=2925,TotalExpireTime=300} + +TypeID = 2924 +Name = "a torch" +Flags = {ChangeUse,Take,ExpireStop} +Attributes = {ChangeTarget=2925,Weight=400,Brightness=0,LightColor=215} + +TypeID = 2925 +Name = "a lit torch" +Flags = {ChangeUse,Take,Expire} +Attributes = {ChangeTarget=2924,Weight=400,Brightness=5,LightColor=206,ExpireTarget=2926,TotalExpireTime=300} + +TypeID = 2926 +Name = "a burnt down torch" +Flags = {Take,Expire} +Attributes = {Weight=350,Brightness=0,LightColor=215,ExpireTarget=0,TotalExpireTime=300} + +TypeID = 2927 +Name = "a lit candelabrum" +Flags = {ChangeUse,Take} +Attributes = {ChangeTarget=2911,Weight=5000,Brightness=6,LightColor=206} + +TypeID = 2928 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2929,Brightness=0,LightColor=0} + +TypeID = 2929 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2928,Brightness=6,LightColor=206} + +TypeID = 2930 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2931,Brightness=0,LightColor=0} + +TypeID = 2931 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2930,Brightness=6,LightColor=206} + +TypeID = 2932 +Name = "an oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=1400,Brightness=0,LightColor=204} + +TypeID = 2933 +Name = "a small oil lamp" +Flags = {Take,ExpireStop} +Attributes = {Weight=900,Brightness=0,LightColor=204} + +TypeID = 2934 +Name = "a tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2935} + +TypeID = 2935 +Name = "a lit tablelamp" +Flags = {ChangeUse} +Attributes = {ChangeTarget=2934,Brightness=4,LightColor=207} + +TypeID = 2936 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2937,Brightness=0,LightColor=0} + +TypeID = 2937 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2936,Brightness=6,LightColor=207} + +TypeID = 2938 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2939,Brightness=0,LightColor=0} + +TypeID = 2939 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2938,Brightness=6,LightColor=207} + +TypeID = 2940 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2941,Brightness=0,LightColor=0} + +TypeID = 2941 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2940,Brightness=6,LightColor=206} + +TypeID = 2942 +Name = "a torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2943,Brightness=0,LightColor=0} + +TypeID = 2943 +Name = "a lit torch bearer" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2942,Brightness=6,LightColor=206} + +TypeID = 2944 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2945,Brightness=0,LightColor=0} + +TypeID = 2945 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2944,Brightness=6,LightColor=207} + +TypeID = 2946 +Name = "a wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2947,Brightness=0,LightColor=0} + +TypeID = 2947 +Name = "a lit wall lamp" +Flags = {ChangeUse,Unmove,Hang} +Attributes = {ChangeTarget=2946,Brightness=6,LightColor=207} + +TypeID = 2948 +Name = "a wooden flute" +Flags = {UseEvent,Take} +Attributes = {Weight=200} + +TypeID = 2949 +Name = "a lyre" +Flags = {UseEvent,Take} +Attributes = {Weight=1250} + +TypeID = 2950 +Name = "a lute" +Flags = {UseEvent,Take} +Attributes = {Weight=3400} + +TypeID = 2951 +Name = "a bongo drum" +Flags = {Take} +Attributes = {Weight=2900} + +TypeID = 2952 +Name = "a drum" +Flags = {UseEvent,Take} +Attributes = {Weight=3200} + +TypeID = 2953 +Name = "panpipes" +Flags = {UseEvent,Take} +Attributes = {Weight=820} + +TypeID = 2954 +Name = "a simple fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 2955 +Name = "a fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2300} + +TypeID = 2956 +Name = "a royal fanfare" +Flags = {UseEvent,Take} +Attributes = {Weight=2500} + +TypeID = 2957 +Name = "a post horn" +Description = "It's property of the Postmaster's Guild and only rewarded to loyal members" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2958 +Name = "a war horn" +Flags = {UseEvent,Take} +Attributes = {Weight=1500} + +TypeID = 2959 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2962,DestroyTarget=3139} + +TypeID = 2960 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2961,DestroyTarget=3139} + +TypeID = 2961 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2959,DestroyTarget=3139} + +TypeID = 2962 +Name = "a piano" +Flags = {UseEvent,Unpass,Unlay,Rotate,Destroy} +Attributes = {RotateTarget=2960,DestroyTarget=3139} + +TypeID = 2963 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2964,DestroyTarget=3136} + +TypeID = 2964 +Name = "a harp" +Flags = {UseEvent,Unpass,Rotate,Destroy} +Attributes = {RotateTarget=2963,DestroyTarget=3136} + +TypeID = 2965 +Name = "a didgeridoo" +Flags = {UseEvent,Take} +Attributes = {Weight=4200} + +TypeID = 2966 +Name = "a war drum" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 2967 +Name = "a magical key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2968 +Name = "a wooden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2969 +Name = "a silver key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2970 +Name = "a copper key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2971 +Name = "a crystal key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2972 +Name = "a golden key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2973 +Name = "a bone key" +Flags = {MultiUse,Key,Take} +Attributes = {Weight=100} + +TypeID = 2974 +Name = "a water pipe" +Flags = {UseEvent,Take,Destroy} +Attributes = {Weight=6500,DestroyTarget=3143} + +TypeID = 2975 +Name = "a birdcage" +Description = "The poor bird seems to have died from a heart attack" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2976 +Name = "a birdcage" +Description = "You see a little bird inside" +Flags = {UseEvent,Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3140} + +TypeID = 2977 +Name = "a pumpkinhead" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=950} + +TypeID = 2978 +Name = "a pumpkinhead" +Flags = {Take,Expire} +Attributes = {Weight=1250,Brightness=3,LightColor=200,ExpireTarget=2977,TotalExpireTime=3000} + +TypeID = 2979 +Name = "a globe" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3143} + +TypeID = 2980 +Name = "a water pipe" +Flags = {Take,Destroy} +Attributes = {Weight=5600,DestroyTarget=3143} + +TypeID = 2981 +Name = "god flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2982 +Name = "an indoor plant" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3144} + +TypeID = 2983 +Name = "a flower bowl" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2984 +Name = "a honey flower" +Flags = {Avoid,Take} +Attributes = {Weight=1000} + +TypeID = 2985 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2986 +Name = "a christmas tree" +Flags = {Unpass,Unlay,Destroy,Expire} +Attributes = {DestroyTarget=3140,Brightness=4,LightColor=204,ExpireTarget=0,TotalExpireTime=21600} + +TypeID = 2987 +Name = "a potted flower" +Flags = {Avoid,Take,Destroy} +Attributes = {Weight=2300,DestroyTarget=3144} + +TypeID = 2988 +Name = "exotic flowers" +Flags = {Avoid,Take} +Attributes = {Weight=1100} + +TypeID = 2989 +Name = "a wooden doll" +Flags = {Take} +Attributes = {Weight=860} + +TypeID = 2990 +Name = "a football" + +TypeID = 2991 +Name = "a doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 2992 +Name = "a snowball" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=80,Range=7,Attack=0,Defense=0,MissileEffect=13,Fragility=100} + +TypeID = 2993 +Name = "a teddy bear" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 2994 +Name = "a model ship" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 2995 +Name = "a piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2996 +Name = "a broken piggy bank" +Flags = {Take} +Attributes = {Weight=750} + +TypeID = 2997 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2998,DestroyTarget=3137} + +TypeID = 2998 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2999,DestroyTarget=3137} + +TypeID = 2999 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3000,DestroyTarget=3137} + +TypeID = 3000 +Name = "a rocking horse" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=2997,DestroyTarget=3137} + +TypeID = 3001 +Name = "a bear doll" +Flags = {Take} +Attributes = {Weight=590} + +TypeID = 3002 +Name = "a voodoo doll" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3003 +Name = "a rope" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=1800} + +TypeID = 3004 +Name = "a wedding ring" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3005 +Name = "an elven brooch" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 3006 +Name = "a ring of the sky" +Flags = {Take} +Attributes = {Weight=40,SlotType=RING} + +TypeID = 3007 +Name = "a crystal ring" +Flags = {Take} +Attributes = {Weight=90,SlotType=RING} + +TypeID = 3008 +Name = "a crystal necklace" +Flags = {Take} +Attributes = {Weight=490,SlotType=NECKLACE} + +TypeID = 3009 +Name = "a bronze necklace" +Flags = {Take} +Attributes = {Weight=410,SlotType=NECKLACE} + +TypeID = 3010 +Name = "an emerald bangle" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3011 +Name = "a crown" +Flags = {Take} +Attributes = {Weight=1900,SlotType=HEAD} + +TypeID = 3012 +Name = "a wolf tooth chain" +Flags = {Take} +Attributes = {Weight=330,SlotType=NECKLACE} + +TypeID = 3013 +Name = "a golden amulet" +Description = "Many gems glitter on the amulet" +Flags = {Take} +Attributes = {Weight=830,SlotType=NECKLACE} + +TypeID = 3014 +Name = "a star amulet" +Flags = {Take} +Attributes = {Weight=610,SlotType=NECKLACE} + +TypeID = 3015 +Name = "a silver necklace" +Flags = {Take} +Attributes = {Weight=480,SlotType=NECKLACE} + +TypeID = 3016 +Name = "a ruby necklace" +Flags = {Take} +Attributes = {Weight=570,SlotType=NECKLACE} + +TypeID = 3017 +Name = "a silver brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3018 +Name = "a scarab amulet" +Flags = {Take} +Attributes = {Weight=770,SlotType=NECKLACE} + +TypeID = 3019 +Name = "a demonbone amulet" +Flags = {Take} +Attributes = {Weight=690,SlotType=NECKLACE} + +TypeID = 3020 +Name = "some golden fruits" +Flags = {Take} +Attributes = {Weight=1070} + +TypeID = 3021 +Name = "a saphire amulet" +Flags = {Take} +Attributes = {Weight=680,SlotType=NECKLACE} + +TypeID = 3022 +Name = "an ancient tiara" +Flags = {Take} +Attributes = {Weight=820,SlotType=HEAD} + +TypeID = 3023 +Name = "a holy scarab" +Flags = {Take} +Attributes = {Weight=870} + +TypeID = 3024 +Name = "a holy falcon" +Flags = {Take} +Attributes = {Weight=840} + +TypeID = 3025 +Name = "an ancient amulet" +Flags = {Take} +Attributes = {Weight=840,SlotType=NECKLACE} + +TypeID = 3026 +Name = "a white pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3027 +Name = "a black pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3028 +Name = "a small diamond" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3029 +Name = "a small sapphire" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3030 +Name = "a small ruby" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3031 +Name = "a gold coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3032 +Name = "a small emerald" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3033 +Name = "a small amethyst" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3034 +Name = "a talon" +Description = "There are many rumours about these magic gems" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3035 +Name = "a platinum coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3036 +Name = "a violet gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3037 +Name = "a yellow gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3038 +Name = "a green gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3039 +Name = "a red gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3040 +Name = "a gold nugget" +Flags = {Cumulative,Take} +Attributes = {Weight=80} + +TypeID = 3041 +Name = "a blue gem" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3042 +Name = "a scarab coin" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3043 +Name = "a crystal coin" +Flags = {Cumulative,Take} +Attributes = {Weight=10} + +TypeID = 3044 +Name = "an elephant tusk" +Flags = {Cumulative,Take} +Attributes = {Weight=1000} + +TypeID = 3045 +Name = "a strange talisman" +Flags = {Take,ShowDetail} +Attributes = {Weight=290,SlotType=NECKLACE,AbsorbEnergy=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3046 +Name = "a magic light wand" +Flags = {ChangeUse,Take,ExpireStop,ShowDetail} +Attributes = {ChangeTarget=3047,Weight=1500,Brightness=0,LightColor=215} + +TypeID = 3047 +Name = "a magic light wand" +Description = "The wand glows" +Flags = {ChangeUse,Take,Expire,ShowDetail} +Attributes = {ChangeTarget=3046,Weight=1500,Brightness=8,LightColor=209,ExpireTarget=0,TotalExpireTime=3000} + +TypeID = 3048 +Name = "a might ring" +Flags = {Take,ShowDetail} +Attributes = {Weight=100,SlotType=RING,AbsorbPhysical=25,AbsorbMagic=25,ExpireTarget=0,TotalUses=20} + +TypeID = 3049 +Name = "a stealth ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=100,SlotType=RING,EquipTarget=3086} + +TypeID = 3050 +Name = "a power ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3087} + +TypeID = 3051 +Name = "an energy ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3088} + +TypeID = 3052 +Name = "a life ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3089} + +TypeID = 3053 +Name = "a time ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3090} + +TypeID = 3054 +Name = "a silver amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbPoison=10,ExpireTarget=0,TotalUses=200} + +TypeID = 3055 +Name = "a platinum amulet" +Description = "It is an amulet of protection" +Flags = {Take,Armor} +Attributes = {Weight=600,SlotType=NECKLACE,ArmorValue=2} + +TypeID = 3056 +Name = "a bronze amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=500,SlotType=NECKLACE,AbsorbManaDrain=15,ExpireTarget=0,TotalUses=200} + +TypeID = 3057 +Name = "an amulet of loss" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3058 +Name = "a strange symbol" +Flags = {MultiUse,Take} +Attributes = {Weight=200,Brightness=2,LightColor=215} + +TypeID = 3059 +Name = "a spellbook" +Flags = {Text,Take} +Attributes = {Weight=5800} + +TypeID = 3060 +Name = "an orb" +Flags = {Take} +Attributes = {Weight=800,Brightness=2,LightColor=26} + +TypeID = 3061 +Name = "a life crystal" +Flags = {Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 3062 +Name = "a mind stone" +Flags = {MultiUse,Take} +Attributes = {Weight=250} + +TypeID = 3063 +Name = "a gold ring" +Flags = {Take} +Attributes = {Weight=100,SlotType=RING} + +TypeID = 3064 +Name = "the orb of nature" +Flags = {Unmove} + +TypeID = 3065 +Name = "a quagmire rod" +Description = "It emits clouds of poisonous swamp gas" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2650,Brightness=2,LightColor=67,Vocations=2,Range=2,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Poison,MissileEffect=15} + +TypeID = 3066 +Name = "a snakebite rod" +Description = "It seems to twitch and quiver as if trying to escape your grip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=4300,Vocations=2,Range=4,ManaConsumption=2,AttackStrength=13,AttackVariation=5,DamageType=Poison,MissileEffect=15} + +TypeID = 3067 +Name = "a tempest rod" +Description = "It grants you the power of striking your foes with furious thunderstorms" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=2100,Brightness=3,LightColor=29,Vocations=2,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Energy,Range=1,MissileEffect=5} + +TypeID = 3068 +Name = "a crystal wand" +Flags = {Take} +Attributes = {Weight=2800} + +TypeID = 3069 +Name = "a volcanic rod" +Description = "It erupts powerful bursts of magma upon everything in your path" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2900,Brightness=2,LightColor=199,Vocations=2,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3070 +Name = "a moonlight rod" +Description = "Shimmering rays of moonlight radiate from its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=1950,Brightness=3,LightColor=143,Vocations=2,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Energy,Range=2,MissileEffect=5} + +TypeID = 3071 +Name = "a wand of inferno" +Description = "It unleashes the very fires of hell" +Flags = {Take,Wand} +Attributes = {MinimumLevel=33,Weight=3050,Brightness=3,LightColor=205,Vocations=1,ManaConsumption=13,AttackStrength=65,AttackVariation=9,DamageType=Fire,Range=2,MissileEffect=4} + +TypeID = 3072 +Name = "a wand of plague" +Description = "Infectious goo covers its tip" +Flags = {Take,Wand} +Attributes = {MinimumLevel=19,Weight=2300,Brightness=2,LightColor=67,Vocations=1,ManaConsumption=5,AttackStrength=30,AttackVariation=7,DamageType=Poison,Range=2,MissileEffect=15} + +TypeID = 3073 +Name = "a wand of cosmic energy" +Description = "The energy of a radiant star is trapped inside its globe" +Flags = {Take,Wand} +Attributes = {MinimumLevel=26,Weight=2300,Brightness=2,LightColor=205,Vocations=1,ManaConsumption=8,AttackStrength=45,AttackVariation=8,DamageType=Energy,Range=1,MissileEffect=5} + +TypeID = 3074 +Name = "a wand of vortex" +Description = "Surges of energy rush through the tip of this wand" +Flags = {Take,Wand} +Attributes = {MinimumLevel=7,Weight=2300,Brightness=2,LightColor=23,Vocations=1,ManaConsumption=2,AttackStrength=13,AttackVariation=5,DamageType=Energy,Range=3,MissileEffect=5} + +TypeID = 3075 +Name = "a wand of dragonbreath" +Description = "Legends say that this wand holds the soul of a young dragon" +Flags = {Take,Wand} +Attributes = {MinimumLevel=13,Weight=2300,Brightness=2,LightColor=192,Vocations=1,ManaConsumption=3,AttackStrength=19,AttackVariation=6,DamageType=Fire,Range=3,MissileEffect=4} + +TypeID = 3076 +Name = "a crystal ball" +Flags = {Take} +Attributes = {Weight=3400} + +TypeID = 3077 +Name = "an ankh" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3078 +Name = "a mysterious fetish" +Flags = {MultiUse,Take} +Attributes = {Weight=490} + +TypeID = 3079 +Name = "boots of haste" +Flags = {Take} +Attributes = {Weight=750,SlotType=FEET,SpeedBoost=20} + +TypeID = 3080 +Name = "a broken amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3081 +Name = "a stone skin amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=700,SlotType=NECKLACE,AbsorbPhysical=80,ExpireTarget=0,TotalUses=5} + +TypeID = 3082 +Name = "an elven amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=270,SlotType=NECKLACE,AbsorbPhysical=10,AbsorbMagic=10,ExpireTarget=0,TotalUses=50} + +TypeID = 3083 +Name = "a garlic necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=380,SlotType=NECKLACE,AbsorbLifeDrain=20,ExpireTarget=0,TotalUses=150} + +TypeID = 3084 +Name = "a protection amulet" +Flags = {Take,ShowDetail} +Attributes = {Weight=550,SlotType=NECKLACE,AbsorbPhysical=6,ExpireTarget=0,TotalUses=250} + +TypeID = 3085 +Name = "a dragon necklace" +Flags = {Take,ShowDetail} +Attributes = {Weight=630,SlotType=NECKLACE,AbsorbFire=8,ExpireTarget=0,TotalUses=200} + +TypeID = 3086 +Name = "a stealth ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=100,SlotType=RING,Invisible=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3049} + +TypeID = 3087 +Name = "a power ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,FistBoost=6,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3050} + +TypeID = 3088 +Name = "an energy ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,ManaShield=1,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3051} + +TypeID = 3089 +Name = "a life ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=80,SlotType=RING,HealthTicks=2000,HealthGain=3,ManaTicks=2000,ManaGain=3,ExpireTarget=0,TotalExpireTime=1200,DeEquipTarget=3052} + +TypeID = 3090 +Name = "a time ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SpeedBoost=30,ExpireTarget=0,TotalExpireTime=600,DeEquipTarget=3053} + +TypeID = 3091 +Name = "a sword ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3094} + +TypeID = 3092 +Name = "an axe ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3095} + +TypeID = 3093 +Name = "a club ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=90,SlotType=RING,EquipTarget=3096} + +TypeID = 3094 +Name = "a sword ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,SwordBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3091} + +TypeID = 3095 +Name = "an axe ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,AxeBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3092} + +TypeID = 3096 +Name = "a club ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=90,SlotType=RING,ClubBoost=4,ExpireTarget=0,TotalExpireTime=1800,DeEquipTarget=3093} + +TypeID = 3097 +Name = "a dwarven ring" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=110,SlotType=RING,EquipTarget=3099} + +TypeID = 3098 +Name = "a ring of healing" +Flags = {Take,ExpireStop,ShowDetail} +Attributes = {Weight=80,SlotType=RING,EquipTarget=3100} + +TypeID = 3099 +Name = "a dwarven ring" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=110,SlotType=RING,SuppressDrunk=1,ExpireTarget=0,TotalExpireTime=3600,DeEquipTarget=3097} + +TypeID = 3100 +Name = "a ring of healing" +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" +Flags = {Text,Take} +Attributes = {Weight=5800} + +TypeID = 3102 +Name = "a paw amulet" +Flags = {Take} +Attributes = {Weight=420,SlotType=NECKLACE} + +TypeID = 3103 +Name = "a cornucopia" +Flags = {UseEvent,Take} +Attributes = {Weight=1400} + +TypeID = 3104 +Name = "a banana skin" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3105 +Name = "a dirty fur" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3106 +Name = "an old twig" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3107 +Name = "some wood" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3108 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3109 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3110 +Name = "a piece of iron" +Flags = {Take} +Attributes = {Weight=20} + +TypeID = 3111 +Name = "a fishbone" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3112 +Name = "rotten meat" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3113 +Name = "broken pottery" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3114 +Name = "a skull" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3115 +Name = "a bone" +Flags = {Take} +Attributes = {Weight=950} + +TypeID = 3116 +Name = "a big bone" +Flags = {Take} +Attributes = {Weight=1900} + +TypeID = 3117 +Name = "broken brown glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3118 +Name = "broken green glass" +Flags = {Take} +Attributes = {Weight=170} + +TypeID = 3119 +Name = "a broken sword" +Flags = {Take} +Attributes = {Weight=3500} + +TypeID = 3120 +Name = "a moldy cheese" +Flags = {Take} +Attributes = {Weight=400} + +TypeID = 3121 +Name = "a torn book" +Flags = {Take} +Attributes = {Weight=1100} + +TypeID = 3122 +Name = "a dirty cape" +Flags = {Take} +Attributes = {Weight=2950} + +TypeID = 3123 +Name = "worn leather boots" +Flags = {Take} +Attributes = {Weight=900} + +TypeID = 3124 +Name = "a burnt scroll" +Flags = {Take} +Attributes = {Weight=40} + +TypeID = 3125 +Name = "remains of a fish" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3126 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3127 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3128 +Name = "rubbish" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3129 +Name = "some leaves" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3130 +Name = "twigs" +Flags = {Take} +Attributes = {Weight=210} + +TypeID = 3131 +Name = "burnt down firewood" +Flags = {Take} +Attributes = {Weight=420} + +TypeID = 3132 +Name = "an animal skull" +Flags = {Unmove} + +TypeID = 3133 +Name = "humanoid remains" +Flags = {Unmove} + +TypeID = 3134 +Name = "ashes" +Flags = {Unmove} + +TypeID = 3135 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3136 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3137 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3138 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3139 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3140 +Name = "wooden trash" +Flags = {Take} +Attributes = {Weight=570} + +TypeID = 3141 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3142 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3143 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3144 +Name = "stone rubbish" +Flags = {Take} +Attributes = {Weight=980} + +TypeID = 3145 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2296,TotalExpireTime=120} + +TypeID = 3146 +Name = "trashed wooden bars" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=2295,TotalExpireTime=120} + +TypeID = 3147 +Name = "a blank rune" +Flags = {Take} +Attributes = {Weight=120} + +TypeID = 3148 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3149 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3150 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3151 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3152 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3153 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3154 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3155 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3156 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3157 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3158 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3159 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3160 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3161 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3162 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3163 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3164 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3165 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3166 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3167 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3168 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3169 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3170 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3171 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3172 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3173 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3174 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3175 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3176 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3177 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3178 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3179 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120} + +TypeID = 3180 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3181 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3182 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3183 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3184 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3185 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3186 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3187 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=2,LightColor=215} + +TypeID = 3188 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3189 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3190 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3191 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3192 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3193 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3194 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3195 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3196 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3197 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3198 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3199 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3200 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3201 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3202 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3203 +Name = "a spell rune" +Flags = {MultiUse,DistUse,Rune,Take} +Attributes = {Weight=120,Brightness=1,LightColor=215} + +TypeID = 3204 +Name = "your own dead body" +Flags = {Unmove} + +TypeID = 3205 +Name = "a family brooch" +Description = "You see the familyname Windtrouser engraved on this brooch" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 3206 +Name = "a dragonfetish" +Flags = {Take} +Attributes = {Weight=490} + +TypeID = 3207 +Name = "the skull of Ratha" +Flags = {Cumulative,Take} +Attributes = {Weight=2180} + +TypeID = 3208 +Name = "a giant smithhammer" +Description = "This cyclopean hammer seems to be an awesome smithing tool" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3209 +Name = "a voodoodoll" +Description = "This voodoodoll looks like a little king" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 3210 +Name = "a hat of the mad" +Description = "You have a vague feeling that it looks somewhat silly" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3211 +Name = "a witchesbroom" +Description = "Don't use it without flying license. Not suitable for minors" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3212 +Name = "a monks diary" +Flags = {Text,Take} +Attributes = {Weight=1300} + +TypeID = 3213 +Name = "an annihilation bear" +Description = "I braved the Annihilator and all I got is this lousy teddy bear" +Flags = {Take} +Attributes = {Weight=4300} + +TypeID = 3214 +Name = "a blessed ankh" +Description = "You see the engraving of a white raven on its surface" +Flags = {MultiUse,Take} +Attributes = {Weight=420} + +TypeID = 3215 +Name = "a phoenix egg" +Description = "It seems to be burning from inside" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3216 +Name = "a bill" +Description = "This is a bill for an expensive magicians hat and several rabbits" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3217 +Name = "a letterbag" +Description = "This bag is nearly bursting from all the letters inside" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=50000,SlotType=BACKPACK} + +TypeID = 3218 +Name = "a present" +Flags = {UseEvent,Take} +Attributes = {Weight=1200} + +TypeID = 3219 +Name = "Waldos Posthorn" +Flags = {UseEvent,Take} +Attributes = {Weight=2200} + +TypeID = 3220 +Name = "a letter to Markwin" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3221 +Name = "Santa's Mailbox" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3222 +Name = "a helmet ornament" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=160} + +TypeID = 3223 +Name = "a gem holder" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3224 +Name = "a right horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3225 +Name = "a left horn" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=140} + +TypeID = 3226 +Name = "a damaged helmet" +Description = "This item seems to have several parts missing" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=HEAD,ArmorValue=5} + +TypeID = 3227 +Name = "a helmet piece" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=260} + +TypeID = 3228 +Name = "a helmet adornement" +Description = "This item seems to be a single part of a bigger object" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 3229 +Name = "a helmet of the ancients" +Description = "The gem of the helmet is burned out and should be replaced" +Flags = {MultiUse,UseEvent,Take,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ArmorValue=8} + +TypeID = 3230 +Name = "a helmet of the ancients" +Description = "The gem is glowing with power" +Flags = {Take,Expire,Armor} +Attributes = {Weight=2760,SlotType=HEAD,ExpireTarget=3229,TotalExpireTime=1800,ArmorValue=11} + +TypeID = 3231 +Name = "a gemmed lamp" +Description = "It is Fa'hradin's enchanted lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3232 +Name = "a spyreport" +Description = "The report is written in some coded language" +Flags = {Take} +Attributes = {Weight=150} + +TypeID = 3233 +Name = "a tear of daraman" +Flags = {Take} +Attributes = {Weight=30} + +TypeID = 3234 +Name = "a cookbook" +Description = "It contains several exotic recipes" +Flags = {Take} +Attributes = {Weight=1500} + +TypeID = 3235 +Name = "an ancient rune" +Description = "This rune vibrates with ancient powers. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=300,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3236 +Name = "blue note" +Description = "The blue crystal is softly humming a ghostly melody. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=250,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3237 +Name = "a sword hilt" +Description = "This was once part of a formidable two handed weapon. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3238 +Name = "a cobrafang dagger" +Description = "This ritual weapon was forged from the sharp fang of a giant cobra. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=600,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3239 +Name = "a crystal arrow" +Description = "This arrow seems not suitable for the use with ordinary bows. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=100,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3240 +Name = "a burning heart" +Description = "The burning heart is still beating with unholy life. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=400,Brightness=1,LightColor=193,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3241 +Name = "an ornamented ankh" +Description = "This ancient relic shows signs of untold age. It seems to be rotting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=500,ExpireTarget=0,TotalExpireTime=65000} + +TypeID = 3242 +Name = "a stuffed bunny" +Flags = {Take} +Attributes = {Weight=350} + +TypeID = 3243 +Name = "a gemmed lamp" +Description = "It is the djinn leader's sleeping lamp" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 3244 +Name = "an old and used backpack" +Description = "A label on the backpack reads: Property of Sam, Thais" +Flags = {Container,Take} +Attributes = {Capacity=20,Weight=1800,SlotType=BACKPACK} + +TypeID = 3245 +Name = "a ring of wishes" +Description = "(This item has 3 charges left)" +Flags = {Take} +Attributes = {Weight=50,SlotType=RING} + +TypeID = 3246 +Name = "boots of waterwalking" +Description = "(This item has 5 charges left)" +Flags = {Take} +Attributes = {Weight=770,SlotType=FEET} + +TypeID = 3247 +Name = "a djinn's lamp" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 3248 +Name = "a portable hole" +Description = "(This item has 1 charge left)" +Flags = {Unmove} + +TypeID = 3249 +Name = "frozen starlight" +Flags = {Take} +Attributes = {Weight=20,Brightness=6,LightColor=29} + +TypeID = 3250 +Name = "the carrot of doom" +Description = "You can sense the evil power of the carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3251 +Name = "a blood orb" +Description = "(This item has 2 charges left)" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3252 +Name = "the horn of sundering" +Description = "(This items has 2 charges left)" +Flags = {Take} +Attributes = {Weight=2300} + +TypeID = 3253 +Name = "a backpack of holding" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3254 +Name = "a roc feather" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3255 +Name = "a drum" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3256 +Name = "a trumpet" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3257 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3258 +Name = "a mandolin" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3259 +Name = "a horn" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3260 +Name = "a lyre" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3261 +Name = "a panpipe" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3262 +Name = "a flute" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3263 +Name = "a gemmed lamp" +Flags = {UseEvent,Unpass,Unmove,Unlay} + +TypeID = 3264 +Name = "a sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=14,Defense=12} + +TypeID = 3265 +Name = "a two handed sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=30,Defense=25} + +TypeID = 3266 +Name = "a battle axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,SlotType=TWOHANDED,WeaponType=AXE,Attack=25,Defense=10} + +TypeID = 3267 +Name = "a dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=950,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3268 +Name = "a hand axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1800,WeaponType=AXE,Attack=10,Defense=5} + +TypeID = 3269 +Name = "a halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=14} + +TypeID = 3270 +Name = "a club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=CLUB,Attack=7,Defense=7} + +TypeID = 3271 +Name = "a spike sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=SWORD,Attack=24,Defense=21} + +TypeID = 3272 +Name = "a rapier" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,WeaponType=SWORD,Attack=10,Defense=8} + +TypeID = 3273 +Name = "a sabre" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2500,WeaponType=SWORD,Attack=12,Defense=10} + +TypeID = 3274 +Name = "an axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=AXE,Attack=12,Defense=6} + +TypeID = 3275 +Name = "a double axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3276 +Name = "a hatchet" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=AXE,Attack=15,Defense=8} + +TypeID = 3277 +Name = "a spear" +Flags = {Cumulative,Take,Distance} +Attributes = {Weight=2000,Range=7,Attack=25,Defense=0,MissileEffect=1,Fragility=3} + +TypeID = 3278 +Name = "a magic longsword" +Description = "It's the magic Cyclopmania Sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4300,SlotType=TWOHANDED,WeaponType=SWORD,Attack=55,Defense=40} + +TypeID = 3279 +Name = "a war hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8500,SlotType=TWOHANDED,WeaponType=CLUB,Attack=45,Defense=10} + +TypeID = 3280 +Name = "a fire sword" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2300,Brightness=3,LightColor=199,WeaponType=SWORD,Attack=35,Defense=20} + +TypeID = 3281 +Name = "a giant sword" +Description = "This sword has been forged by ancient giants" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=18000,SlotType=TWOHANDED,WeaponType=SWORD,Attack=46,Defense=22} + +TypeID = 3282 +Name = "a morning star" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5400,WeaponType=CLUB,Attack=25,Defense=11} + +TypeID = 3283 +Name = "a carlin sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=SWORD,Attack=15,Defense=13} + +TypeID = 3284 +Name = "an ice rapier" +Description = "A deadly but fragile weapon" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1500,ExpireTarget=0,TotalUses=1,WeaponType=SWORD,Attack=100,Defense=1} + +TypeID = 3285 +Name = "a longsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=17,Defense=14} + +TypeID = 3286 +Name = "a mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,WeaponType=CLUB,Attack=16,Defense=11} + +TypeID = 3287 +Name = "a throwing star" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=200,Range=7,Attack=35,Defense=0,MissileEffect=8,Fragility=10} + +TypeID = 3288 +Name = "a magic sword" +Description = "It's the Sword of Valor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4200,WeaponType=SWORD,Attack=48,Defense=35} + +TypeID = 3289 +Name = "a staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,WeaponType=CLUB,Attack=10,Defense=25} + +TypeID = 3290 +Name = "a silver dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1020,WeaponType=SWORD,Attack=8,Defense=7} + +TypeID = 3291 +Name = "a knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=420,WeaponType=SWORD,Attack=7,Defense=5} + +TypeID = 3292 +Name = "a combat knife" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=870,WeaponType=SWORD,Attack=8,Defense=6} + +TypeID = 3293 +Name = "a sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1050,WeaponType=AXE,Attack=7,Defense=4} + +TypeID = 3294 +Name = "a short sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=SWORD,Attack=11,Defense=11} + +TypeID = 3295 +Name = "a bright sword" +Description = "The blade shimmers in light blue" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,Brightness=2,LightColor=143,WeaponType=SWORD,Attack=36,Defense=30} + +TypeID = 3296 +Name = "a warlord sword" +Description = "Strong powers flow in this magic sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=SWORD,Attack=53,Defense=38} + +TypeID = 3297 +Name = "a serpent sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=SWORD,Attack=26,Defense=15} + +TypeID = 3298 +Name = "a throwing knife" +Flags = {MultiUse,Cumulative,Take,Distance} +Attributes = {Weight=500,Range=7,Attack=25,Defense=0,MissileEffect=9,Fragility=7} + +TypeID = 3299 +Name = "a poison dagger" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=880,WeaponType=SWORD,Attack=18,Defense=8} + +TypeID = 3300 +Name = "a katana" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3100,WeaponType=SWORD,Attack=16,Defense=12} + +TypeID = 3301 +Name = "a broadsword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=SWORD,Attack=26,Defense=23} + +TypeID = 3302 +Name = "a dragon lance" +Description = "The extraordinary sharp blade penetrates every armor" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,SlotType=TWOHANDED,WeaponType=AXE,Attack=47,Defense=16} + +TypeID = 3303 +Name = "a great axe" +Description = "A masterpiece of a dwarven smith" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9000,SlotType=TWOHANDED,WeaponType=AXE,Attack=52,Defense=22} + +TypeID = 3304 +Name = "a crowbar" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=2100,WeaponType=CLUB,Attack=6,Defense=6} + +TypeID = 3305 +Name = "a battle hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=24,Defense=14} + +TypeID = 3306 +Name = "a golden sickle" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1950,WeaponType=AXE,Attack=13,Defense=6} + +TypeID = 3307 +Name = "a scimitar" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=19,Defense=13} + +TypeID = 3308 +Name = "a machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1650,WeaponType=SWORD,Attack=12,Defense=9} + +TypeID = 3309 +Name = "a thunder hammer" +Description = "It is blessed by the gods of Tibia" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=12500,WeaponType=CLUB,Attack=49,Defense=35} + +TypeID = 3310 +Name = "an iron hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6600,WeaponType=CLUB,Attack=18,Defense=10} + +TypeID = 3311 +Name = "a clerical mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5800,WeaponType=CLUB,Attack=28,Defense=15} + +TypeID = 3312 +Name = "a silver mace" +Description = "You feel an aura of protection" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6700,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3313 +Name = "an obsidian lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=34,Defense=10} + +TypeID = 3314 +Name = "a naginata" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7800,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=25} + +TypeID = 3315 +Name = "a guardian halberd" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=AXE,Attack=46,Defense=15} + +TypeID = 3316 +Name = "an orcish axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4500,WeaponType=AXE,Attack=23,Defense=12} + +TypeID = 3317 +Name = "a barbarian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5100,WeaponType=AXE,Attack=28,Defense=18} + +TypeID = 3318 +Name = "a knight axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5900,WeaponType=AXE,Attack=33,Defense=21} + +TypeID = 3319 +Name = "a stonecutter axe" +Description = "You feel the power of this mighty axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9900,WeaponType=AXE,Attack=50,Defense=30} + +TypeID = 3320 +Name = "a fire axe" +Description = "The blade is a magic flame" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,Brightness=3,LightColor=199,WeaponType=AXE,Attack=38,Defense=16} + +TypeID = 3321 +Name = "an enchanted staff" +Description = "Temporal magic powers enchant this staff" +Flags = {MultiUse,Take,Expire,Weapon} +Attributes = {Weight=3800,SlotType=TWOHANDED,ExpireTarget=3289,TotalExpireTime=60,WeaponType=CLUB,Attack=39,Defense=45} + +TypeID = 3322 +Name = "a dragon hammer" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=9700,WeaponType=CLUB,Attack=32,Defense=20} + +TypeID = 3323 +Name = "a dwarven axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8200,WeaponType=AXE,Attack=31,Defense=19} + +TypeID = 3324 +Name = "a skull staff" +Description = "The staff longs for death" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1700,Brightness=2,LightColor=180,WeaponType=CLUB,Attack=36,Defense=12} + +TypeID = 3325 +Name = "a light mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=14,Defense=9} + +TypeID = 3326 +Name = "a foil" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1450,WeaponType=SWORD,Attack=9,Defense=11} + +TypeID = 3327 +Name = "a daramanian mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6800,WeaponType=CLUB,Attack=21,Defense=12} + +TypeID = 3328 +Name = "a daramanian waraxe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=39,Defense=15} + +TypeID = 3329 +Name = "a daramanian axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=AXE,Attack=16,Defense=8} + +TypeID = 3330 +Name = "a heavy machete" +Flags = {MultiUse,UseEvent,Take,Weapon} +Attributes = {Weight=1840,WeaponType=SWORD,Attack=16,Defense=10} + +TypeID = 3331 +Name = "a ravager's axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5250,SlotType=TWOHANDED,WeaponType=AXE,Attack=49,Defense=14} + +TypeID = 3332 +Name = "a hammer of wrath" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=7000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=48,Defense=12} + +TypeID = 3333 +Name = "a crystal mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,WeaponType=CLUB,Attack=38,Defense=16} + +TypeID = 3334 +Name = "a pharaoh sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=15000,WeaponType=SWORD,Attack=41,Defense=23} + +TypeID = 3335 +Name = "a twin axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6400,SlotType=TWOHANDED,WeaponType=AXE,Attack=45,Defense=24} + +TypeID = 3336 +Name = "a studded club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3500,WeaponType=CLUB,Attack=9,Defense=8} + +TypeID = 3337 +Name = "a bone club" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=3900,WeaponType=CLUB,Attack=12,Defense=8} + +TypeID = 3338 +Name = "a bone sword" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=1900,WeaponType=SWORD,Attack=14,Defense=10} + +TypeID = 3339 +Name = "a djinn blade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2450,WeaponType=SWORD,Attack=38,Defense=22} + +TypeID = 3340 +Name = "a heavy mace" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=11000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=50,Defense=15} + +TypeID = 3341 +Name = "an arcane staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4000,WeaponType=CLUB,Attack=50,Defense=30} + +TypeID = 3342 +Name = "a war axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=20,Defense=10} + +TypeID = 3343 +Name = "a lich staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=4100,WeaponType=CLUB,Attack=40,Defense=30} + +TypeID = 3344 +Name = "a beastslayer axe" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=6150,WeaponType=AXE,Attack=35,Defense=12} + +TypeID = 3345 +Name = "a templar scytheblade" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=2900,WeaponType=SWORD,Attack=23,Defense=15} + +TypeID = 3346 +Name = "a ripper lance" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=28,Defense=7} + +TypeID = 3347 +Name = "a hunting spear" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=8000,SlotType=TWOHANDED,WeaponType=AXE,Attack=18,Defense=8} + +TypeID = 3348 +Name = "a banana staff" +Flags = {MultiUse,Take,Weapon} +Attributes = {Weight=5000,WeaponType=CLUB,Attack=25,Defense=15} + +TypeID = 3349 +Name = "a crossbow" +Flags = {Take,Distance} +Attributes = {Weight=4000,SlotType=TWOHANDED,Range=7,AmmoType=BOLT} + +TypeID = 3350 +Name = "a bow" +Flags = {Take,Distance} +Attributes = {Weight=3100,SlotType=TWOHANDED,Range=7,AmmoType=ARROW} + +TypeID = 3351 +Name = "a steel helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3352 +Name = "a chain helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3353 +Name = "an iron helmet" +Flags = {Take,Armor} +Attributes = {Weight=3000,SlotType=HEAD,ArmorValue=5} + +TypeID = 3354 +Name = "a brass helmet" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=HEAD,ArmorValue=3} + +TypeID = 3355 +Name = "a leather helmet" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=1} + +TypeID = 3356 +Name = "a devil helmet" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=HEAD,ArmorValue=7} + +TypeID = 3357 +Name = "a plate armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3358 +Name = "a chain armor" +Flags = {Take,Armor} +Attributes = {Weight=10000,SlotType=BODY,ArmorValue=6} + +TypeID = 3359 +Name = "a brass armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=8} + +TypeID = 3360 +Name = "a golden armor" +Description = "It's an enchanted armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=14} + +TypeID = 3361 +Name = "a leather armor" +Flags = {Take,Armor} +Attributes = {Weight=6000,SlotType=BODY,ArmorValue=4} + +TypeID = 3362 +Name = "studded legs" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=LEGS,ArmorValue=2} + +TypeID = 3363 +Name = "dragon scale legs" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=LEGS,ArmorValue=10} + +TypeID = 3364 +Name = "golden legs" +Flags = {Take,Armor} +Attributes = {Weight=5600,SlotType=LEGS,ArmorValue=9} + +TypeID = 3365 +Name = "a golden helmet" +Description = "It's the famous Helmet of the Stars" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=12} + +TypeID = 3366 +Name = "a magic plate armor" +Description = "An enchanted gem glows on the plate armor" +Flags = {Take,Armor} +Attributes = {Weight=8500,SlotType=BODY,ArmorValue=17} + +TypeID = 3367 +Name = "a viking helmet" +Flags = {Take,Armor} +Attributes = {Weight=3900,SlotType=HEAD,ArmorValue=4} + +TypeID = 3368 +Name = "a winged helmet" +Description = "It's the Helmet of Hermes" +Flags = {Take,Armor} +Attributes = {Weight=1200,SlotType=HEAD,ArmorValue=10} + +TypeID = 3369 +Name = "a warrior helmet" +Flags = {Take,Armor} +Attributes = {Weight=6800,SlotType=HEAD,ArmorValue=8} + +TypeID = 3370 +Name = "a knight armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=12} + +TypeID = 3371 +Name = "knight legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=8} + +TypeID = 3372 +Name = "brass legs" +Flags = {Take,Armor} +Attributes = {Weight=3800,SlotType=LEGS,ArmorValue=5} + +TypeID = 3373 +Name = "a strange helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3374 +Name = "a legion helmet" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=HEAD,ArmorValue=4} + +TypeID = 3375 +Name = "a soldier helmet" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=HEAD,ArmorValue=5} + +TypeID = 3376 +Name = "a studded helmet" +Flags = {Take,Armor} +Attributes = {Weight=2450,SlotType=HEAD,ArmorValue=2} + +TypeID = 3377 +Name = "a scale armor" +Flags = {Take,Armor} +Attributes = {Weight=10500,SlotType=BODY,ArmorValue=9} + +TypeID = 3378 +Name = "a studded armor" +Flags = {Take,Armor} +Attributes = {Weight=7100,SlotType=BODY,ArmorValue=5} + +TypeID = 3379 +Name = "a doublet" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=BODY,ArmorValue=2} + +TypeID = 3380 +Name = "a noble armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=11} + +TypeID = 3381 +Name = "a crown armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3382 +Name = "crown legs" +Flags = {Take,Armor} +Attributes = {Weight=6500,SlotType=LEGS,ArmorValue=8} + +TypeID = 3383 +Name = "a dark armor" +Flags = {Take,Armor} +Attributes = {Weight=12000,SlotType=BODY,ArmorValue=10} + +TypeID = 3384 +Name = "a dark helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=6} + +TypeID = 3385 +Name = "a crown helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3386 +Name = "a dragon scale mail" +Flags = {Take,Armor} +Attributes = {Weight=11400,SlotType=BODY,ArmorValue=15} + +TypeID = 3387 +Name = "a demon helmet" +Description = "You hear an evil whispering from inside" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=10} + +TypeID = 3388 +Name = "a demon armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=16} + +TypeID = 3389 +Name = "demon legs" +Flags = {Take,Armor} +Attributes = {Weight=7000,SlotType=LEGS,ArmorValue=9} + +TypeID = 3390 +Name = "a horned helmet" +Flags = {Take,Armor} +Attributes = {Weight=5100,SlotType=HEAD,ArmorValue=11} + +TypeID = 3391 +Name = "a crusader helmet" +Flags = {Take,Armor} +Attributes = {Weight=5200,SlotType=HEAD,ArmorValue=8} + +TypeID = 3392 +Name = "a royal helmet" +Description = "An excellent masterpiece of a smith" +Flags = {Take,Armor} +Attributes = {Weight=4800,SlotType=HEAD,ArmorValue=9} + +TypeID = 3393 +Name = "an amazon helmet" +Flags = {Take,Armor} +Attributes = {Weight=2950,SlotType=HEAD,ArmorValue=7} + +TypeID = 3394 +Name = "an amazon armor" +Flags = {Take,Armor} +Attributes = {Weight=9900,SlotType=BODY,ArmorValue=13} + +TypeID = 3395 +Name = "a ceremonial mask" +Flags = {Take,Armor} +Attributes = {Weight=4000,SlotType=HEAD,Brightness=3,LightColor=215,ArmorValue=9} + +TypeID = 3396 +Name = "a dwarfen helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3397 +Name = "a dwarven armor" +Flags = {Take,Armor} +Attributes = {Weight=13000,SlotType=BODY,ArmorValue=10} + +TypeID = 3398 +Name = "dwarfen legs" +Flags = {Take,Armor} +Attributes = {Weight=4900,SlotType=LEGS,ArmorValue=6} + +TypeID = 3399 +Name = "an elven mail" +Flags = {Take,Armor} +Attributes = {Weight=9000,SlotType=BODY,ArmorValue=9} + +TypeID = 3400 +Name = "a dragon scale helmet" +Flags = {Take,Armor} +Attributes = {Weight=3250,SlotType=HEAD,ArmorValue=9} + +TypeID = 3401 +Name = "elven legs" +Flags = {Take,Armor} +Attributes = {Weight=3300,SlotType=LEGS,ArmorValue=4} + +TypeID = 3402 +Name = "a native armor" +Flags = {Take,Armor} +Attributes = {Weight=8000,SlotType=BODY,ArmorValue=7} + +TypeID = 3403 +Name = "a tribal mask" +Flags = {Take,Armor} +Attributes = {Weight=2500,SlotType=HEAD,ArmorValue=2} + +TypeID = 3404 +Name = "a leopard armor" +Flags = {Take,Armor} +Attributes = {Weight=9500,SlotType=BODY,ArmorValue=9} + +TypeID = 3405 +Name = "a horseman helmet" +Flags = {Take,Armor} +Attributes = {Weight=4200,SlotType=HEAD,ArmorValue=6} + +TypeID = 3406 +Name = "a feather headdress" +Flags = {Take,Armor} +Attributes = {Weight=2100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3407 +Name = "a charmer's tiara" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=HEAD,ArmorValue=2} + +TypeID = 3408 +Name = "a beholder helmet" +Flags = {Take,Armor} +Attributes = {Weight=4600,SlotType=HEAD,ArmorValue=7} + +TypeID = 3409 +Name = "a steel shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=21} + +TypeID = 3410 +Name = "a plate shield" +Flags = {Take,Shield} +Attributes = {Weight=6500,Defense=17} + +TypeID = 3411 +Name = "a brass shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=16} + +TypeID = 3412 +Name = "a wooden shield" +Flags = {Take,Shield} +Attributes = {Weight=4000,Defense=14} + +TypeID = 3413 +Name = "a battle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=23} + +TypeID = 3414 +Name = "a mastermind shield" +Description = "It's an enchanted shield" +Flags = {Take,Shield} +Attributes = {Weight=5700,Defense=37} + +TypeID = 3415 +Name = "a guardian shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=30} + +TypeID = 3416 +Name = "a dragon shield" +Flags = {Take,Shield} +Attributes = {Weight=6000,Defense=31} + +TypeID = 3417 +Name = "a shield of honour" +Description = "A mighty shield warded by the gods of Tibia" +Flags = {Take,Shield} +Attributes = {Weight=5400,Defense=33} + +TypeID = 3418 +Name = "a beholder shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=28} + +TypeID = 3419 +Name = "a crown shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3420 +Name = "a demon shield" +Description = "This powerful shield seems to be as light as air" +Flags = {Take,Shield} +Attributes = {Weight=2600,Defense=35} + +TypeID = 3421 +Name = "a dark shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=25} + +TypeID = 3422 +Name = "a great shield" +Description = "The shield is made of dragon scales" +Flags = {Take,Shield} +Attributes = {Weight=8400,Defense=38} + +TypeID = 3423 +Name = "a blessed shield" +Description = "The shield grants divine protection" +Flags = {Take,Shield} +Attributes = {Weight=6800,Defense=40} + +TypeID = 3424 +Name = "an ornamented shield" +Description = "Many gems sparkle on the shield" +Flags = {Take,Shield} +Attributes = {Weight=6700,Defense=22} + +TypeID = 3425 +Name = "a dwarven shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=26} + +TypeID = 3426 +Name = "a studded shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=15} + +TypeID = 3427 +Name = "a rose shield" +Flags = {Take,Shield} +Attributes = {Weight=5200,Defense=27} + +TypeID = 3428 +Name = "a tower shield" +Flags = {Take,Shield} +Attributes = {Weight=8200,Defense=32} + +TypeID = 3429 +Name = "a black shield" +Description = "An unholy creature covers the shield" +Flags = {Take,Shield} +Attributes = {Weight=4200,Defense=18} + +TypeID = 3430 +Name = "a copper shield" +Flags = {Take,Shield} +Attributes = {Weight=6300,Defense=19} + +TypeID = 3431 +Name = "a viking shield" +Flags = {Take,Shield} +Attributes = {Weight=6600,Defense=22} + +TypeID = 3432 +Name = "an ancient shield" +Flags = {Take,Shield} +Attributes = {Weight=6100,Defense=27} + +TypeID = 3433 +Name = "a griffin shield" +Flags = {Take,Shield} +Attributes = {Weight=5000,Defense=29} + +TypeID = 3434 +Name = "a vampire shield" +Description = "Dark powers enchant this shield" +Flags = {Take,Shield} +Attributes = {Weight=3800,Defense=34} + +TypeID = 3435 +Name = "a castle shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=28} + +TypeID = 3436 +Name = "a medusa shield" +Flags = {Take,Shield} +Attributes = {Weight=5800,Defense=33} + +TypeID = 3437 +Name = "an amazon shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3438 +Name = "an eagle shield" +Flags = {Take,Shield} +Attributes = {Weight=6200,Defense=32} + +TypeID = 3439 +Name = "a phoenix shield" +Description = "This shield feels warm to the touch" +Flags = {Take,Shield} +Attributes = {Weight=3500,Defense=34} + +TypeID = 3440 +Name = "a scarab shield" +Flags = {Take,Shield} +Attributes = {Weight=4700,Defense=25} + +TypeID = 3441 +Name = "a bone shield" +Flags = {Take,Shield} +Attributes = {Weight=5500,Defense=20} + +TypeID = 3442 +Name = "a tempest shield" +Flags = {Take,Shield} +Attributes = {Weight=5100,Defense=36} + +TypeID = 3443 +Name = "a tusk shield" +Flags = {Take,Shield} +Attributes = {Weight=6900,Defense=27} + +TypeID = 3444 +Name = "a sentinel shield" +Flags = {Take,Shield} +Attributes = {Weight=4900,Defense=22} + +TypeID = 3445 +Name = "a salamander shield" +Flags = {Take,Shield} +Attributes = {Weight=5900,Defense=26} + +TypeID = 3446 +Name = "a bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=BOLT,Attack=30,MissileEffect=2,Fragility=100} + +TypeID = 3447 +Name = "an arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=70,AmmoType=ARROW,Attack=25,MissileEffect=3,Fragility=100} + +TypeID = 3448 +Name = "a poison arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=80,AmmoType=ARROW,Attack=10,MissileEffect=6,Fragility=100,WeaponSpecialEffect=1,AttackStrength=50} + +TypeID = 3449 +Name = "a burst arrow" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=ARROW,Attack=0,MissileEffect=7,Fragility=100,WeaponSpecialEffect=2,AttackStrength=30} + +TypeID = 3450 +Name = "a power bolt" +Flags = {Cumulative,Take,Ammo} +Attributes = {Weight=90,AmmoType=BOLT,Attack=40,MissileEffect=14,Fragility=100} + +TypeID = 3451 +Name = "a pitchfork" +Flags = {MultiUse,Take} +Attributes = {Weight=2500} + +TypeID = 3452 +Name = "a rake" +Flags = {MultiUse,Take} +Attributes = {Weight=1500} + +TypeID = 3453 +Name = "a scythe" +Flags = {UseEvent,MultiUse,Take,Weapon} +Attributes = {Weight=3000,SlotType=TWOHANDED,WeaponType=CLUB,Attack=8,Defense=3} + +TypeID = 3454 +Name = "a broom" +Flags = {MultiUse,Take} +Attributes = {Weight=1100} + +TypeID = 3455 +Name = "a hoe" +Flags = {MultiUse,Take} +Attributes = {Weight=2800} + +TypeID = 3456 +Name = "a pick" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=4500} + +TypeID = 3457 +Name = "a shovel" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=3500} + +TypeID = 3458 +Name = "an anvil" +Flags = {Unpass,Unmove,Height} + +TypeID = 3459 +Name = "a wooden hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 3460 +Name = "a hammer" +Flags = {MultiUse,Take} +Attributes = {Weight=1150} + +TypeID = 3461 +Name = "a saw" +Flags = {MultiUse,Take} +Attributes = {Weight=1000} + +TypeID = 3462 +Name = "a small axe" +Flags = {MultiUse,Take} +Attributes = {Weight=2000} + +TypeID = 3463 +Name = "a mirror" +Flags = {MultiUse,Take} +Attributes = {Weight=950} + +TypeID = 3464 +Name = "a baking tray" +Flags = {Take} +Attributes = {Weight=1200} + +TypeID = 3465 +Name = "a pot" +Flags = {MultiUse,FluidContainer,Unpass,Take,Destroy,Height} +Attributes = {Weight=5250,DestroyTarget=3142} + +TypeID = 3466 +Name = "a pan" +Flags = {Take} +Attributes = {Weight=1800} + +TypeID = 3467 +Name = "a fork" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3468 +Name = "a spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3469 +Name = "a knife" +Flags = {MultiUse,UseEvent,Take} +Attributes = {Weight=100} + +TypeID = 3470 +Name = "a wooden spoon" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 3471 +Name = "a cleaver" +Flags = {Take} +Attributes = {Weight=660} + +TypeID = 3472 +Name = "an oven spatula" +Flags = {Take} +Attributes = {Weight=1400} + +TypeID = 3473 +Name = "a rolling pin" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3474 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3475 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3476 +Name = "a bowel" +Flags = {Take} +Attributes = {Weight=1850} + +TypeID = 3477 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3478 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3479 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3480 +Name = "a ewer" +Flags = {MultiUse,FluidContainer,Take} +Attributes = {Weight=1750} + +TypeID = 3481 +Name = "a closed trap" +Flags = {UseEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3482 +Name = "a trap" +Flags = {UseEvent,CollisionEvent,Take} +Attributes = {Weight=2100} + +TypeID = 3483 +Name = "a fishing rod" +Flags = {MultiUse,DistUse,UseEvent,Take} +Attributes = {Weight=850} + +TypeID = 3484 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3487,DestroyTarget=3137} + +TypeID = 3485 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3486,DestroyTarget=3137} + +TypeID = 3486 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3484,DestroyTarget=3137} + +TypeID = 3487 +Name = "a telescope" +Flags = {Unpass,Unlay,Rotate,Destroy,Height} +Attributes = {RotateTarget=3485,DestroyTarget=3137} + +TypeID = 3488 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3491} + +TypeID = 3489 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3490} + +TypeID = 3490 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3488} + +TypeID = 3491 +Name = "a ships telescope" +Flags = {Unpass,Unmove,Unlay,Rotate,Height} +Attributes = {RotateTarget=3489} + +TypeID = 3492 +Name = "a worm" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3493 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3494 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3495 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3496 +Name = "a crane" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3497 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3498 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3499 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3500 +Name = "a locker" +Flags = {Container,Unpass,Unmove,Height,Depot} +Attributes = {Capacity=30} + +TypeID = 3501 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3502 +Name = "a depot chest" +Flags = {Container,Unmove} +Attributes = {Capacity=30} + +TypeID = 3503 +Name = "a parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3504 +Name = "a stamped parcel" +Flags = {Container,Avoid,Take,Height} +Attributes = {Capacity=10,Weight=1800} + +TypeID = 3505 +Name = "a letter" +Flags = {Text,Write,Take} +Attributes = {MaxLength=2000,Weight=50} + +TypeID = 3506 +Name = "a stamped letter" +Flags = {Text,Take} +Attributes = {Weight=50} + +TypeID = 3507 +Name = "a label" +Flags = {Text,Write,Take} +Attributes = {MaxLength=80,Weight=10} + +TypeID = 3508 +Name = "a mailbox" +Description = "Royal Tibia Mail" +Flags = {Bottom,CollisionEvent,Unpass,Unmove,Height,Mailbox} + +TypeID = 3509 +Name = "an inkwell" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 3510 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=207} + +TypeID = 3511 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3512 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3513 +Name = "a coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=8,LightColor=206} + +TypeID = 3514 +Name = "an empty coal basin" +Flags = {Unpass,Destroy,Height} +Attributes = {DestroyTarget=3143,Brightness=0,LightColor=215} + +TypeID = 3515 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3516 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3517 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3518 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3519 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3520 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,CollisionEvent,Unmove,Height} + +TypeID = 3521 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3522 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3523 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3524 +Name = "an altar" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3525 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3526 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3527 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3528 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3529 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3530 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3531 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3532 +Name = "a sacrificial stone" +Flags = {Bottom,Unpass,Unmove,Height} + +TypeID = 3533 +Name = "a black token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3534 +Name = "a white token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3535 +Name = "a white pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3536 +Name = "a white castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3537 +Name = "a white knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3538 +Name = "a white bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3539 +Name = "the white queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3540 +Name = "the white king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3541 +Name = "a black pawn" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3542 +Name = "a black castle" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3543 +Name = "a black knight" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3544 +Name = "a black bishop" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3545 +Name = "the black queen" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3546 +Name = "the black king" +Flags = {Take} +Attributes = {Weight=500} + +TypeID = 3547 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3548 +Name = "a tic-tac-toe token" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 3549 +Name = "soft boots" +Flags = {Take,Expire,ShowDetail} +Attributes = {Weight=800,SlotType=FEET,ExpireTarget=6530,DeEquipTarget=6529,TotalExpireTime=14400,HealthGain=1,HealthTicks=2000,ManaGain=2,ManaTicks=1000} + +TypeID = 3550 +Name = "patched boots" +Flags = {Take,Armor} +Attributes = {Weight=1000,SlotType=FEET,ArmorValue=2} + +TypeID = 3551 +Name = "sandals" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3552 +Name = "leather boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3553 +Name = "bunnyslippers" +Flags = {Take} +Attributes = {Weight=600,SlotType=FEET} + +TypeID = 3554 +Name = "steel boots" +Flags = {Take,Armor} +Attributes = {Weight=2900,SlotType=FEET,ArmorValue=3} + +TypeID = 3555 +Name = "golden boots" +Flags = {Take,Armor} +Attributes = {Weight=3100,SlotType=FEET,ArmorValue=4} + +TypeID = 3556 +Name = "crocodile boots" +Flags = {Take,Armor} +Attributes = {Weight=900,SlotType=FEET,ArmorValue=1} + +TypeID = 3557 +Name = "plate legs" +Flags = {Take,Armor} +Attributes = {Weight=5000,SlotType=LEGS,ArmorValue=7} + +TypeID = 3558 +Name = "chain legs" +Flags = {Take,Armor} +Attributes = {Weight=3500,SlotType=LEGS,ArmorValue=3} + +TypeID = 3559 +Name = "leather legs" +Flags = {Take,Armor} +Attributes = {Weight=1800,SlotType=LEGS,ArmorValue=1} + +TypeID = 3560 +Name = "a bast skirt" +Flags = {Take} +Attributes = {Weight=350,SlotType=LEGS} + +TypeID = 3561 +Name = "a jacket" +Flags = {Take,Armor} +Attributes = {Weight=2400,SlotType=BODY,ArmorValue=1} + +TypeID = 3562 +Name = "a coat" +Flags = {Take,Armor} +Attributes = {Weight=2700,SlotType=BODY,ArmorValue=1} + +TypeID = 3563 +Name = "a green tunic" +Flags = {Take,Armor} +Attributes = {Weight=930,SlotType=BODY,ArmorValue=1} + +TypeID = 3564 +Name = "a red tunic" +Flags = {Take,Armor} +Attributes = {Weight=1400,SlotType=BODY,ArmorValue=2} + +TypeID = 3565 +Name = "a cape" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3566 +Name = "a red robe" +Description = "The robe is artfully embroidered" +Flags = {Take,Armor} +Attributes = {Weight=2600,SlotType=BODY,ArmorValue=1} + +TypeID = 3567 +Name = "a blue robe" +Description = "It is a magic robe" +Flags = {Take,Armor} +Attributes = {Weight=2200,SlotType=BODY,ArmorValue=11} + +TypeID = 3568 +Name = "a simple dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3569 +Name = "a white dress" +Flags = {Take} +Attributes = {Weight=2400,SlotType=BODY} + +TypeID = 3570 +Name = "a ball gown" +Flags = {Take} +Attributes = {Weight=2500,SlotType=BODY} + +TypeID = 3571 +Name = "a rangers cloak" +Flags = {Take,Armor} +Attributes = {Weight=3200,SlotType=BODY,ArmorValue=1} + +TypeID = 3572 +Name = "a scarf" +Flags = {Take,Armor} +Attributes = {Weight=200,SlotType=NECKLACE,ArmorValue=1} + +TypeID = 3573 +Name = "a magician hat" +Flags = {Take,Armor} +Attributes = {Weight=750,SlotType=HEAD,ArmorValue=1} + +TypeID = 3574 +Name = "a mystic turban" +Description = "Something is strange about this turban" +Flags = {Take,Armor} +Attributes = {Weight=850,SlotType=HEAD,ArmorValue=1} + +TypeID = 3575 +Name = "a wood cape" +Flags = {Take,Armor} +Attributes = {Weight=1100,SlotType=HEAD,ArmorValue=2} + +TypeID = 3576 +Name = "a post officers hat" +Description = "This hat is the insignia of all tibian post officers" +Flags = {Take,Armor} +Attributes = {Weight=700,SlotType=HEAD,ArmorValue=1} + +TypeID = 3577 +Name = "meat" +Flags = {Cumulative,Take} +Attributes = {Nutrition=15,Weight=1300} + +TypeID = 3578 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=520} + +TypeID = 3579 +Name = "salmon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=320} + +TypeID = 3580 +Name = "a fish" +Flags = {Cumulative,Take} +Attributes = {Nutrition=17,Weight=830} + +TypeID = 3581 +Name = "shrimp" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3582 +Name = "ham" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=2000} + +TypeID = 3583 +Name = "dragon ham" +Description = "It still contains a small part of the power of a dragon" +Flags = {Cumulative,Take} +Attributes = {Nutrition=60,Weight=3000} + +TypeID = 3584 +Name = "a pear" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=140} + +TypeID = 3585 +Name = "a red apple" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=150} + +TypeID = 3586 +Name = "an orange" +Flags = {Cumulative,Take} +Attributes = {Nutrition=13,Weight=110} + +TypeID = 3587 +Name = "a banana" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=180} + +TypeID = 3588 +Name = "a blueberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3589 +Name = "a coconut" +Flags = {Cumulative,Take} +Attributes = {Nutrition=18,Weight=480} + +TypeID = 3590 +Name = "a cherry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=1,Weight=20} + +TypeID = 3591 +Name = "a strawberry" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=20} + +TypeID = 3592 +Name = "grapes" +Flags = {Take} +Attributes = {Nutrition=9,Weight=250} + +TypeID = 3593 +Name = "a melon" +Flags = {Take} +Attributes = {Nutrition=20,Weight=950} + +TypeID = 3594 +Name = "a pumpkin" +Flags = {Take} +Attributes = {Nutrition=17,Weight=1350} + +TypeID = 3595 +Name = "a carrot" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=160} + +TypeID = 3596 +Name = "a tomato" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=100} + +TypeID = 3597 +Name = "a corncob" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=350} + +TypeID = 3598 +Name = "a cookie" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=10} + +TypeID = 3599 +Name = "a candy cane" +Flags = {Cumulative,Take} +Attributes = {Nutrition=2,Weight=50} + +TypeID = 3600 +Name = "a bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=10,Weight=500} + +TypeID = 3601 +Name = "a roll" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=100} + +TypeID = 3602 +Name = "a brown bread" +Flags = {Cumulative,Take} +Attributes = {Nutrition=8,Weight=400} + +TypeID = 3603 +Name = "flour" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3604 +Name = "a lump of dough" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=500} + +TypeID = 3605 +Name = "a bunch of wheat" +Flags = {UseEvent,Cumulative,MultiUse,Take} +Attributes = {Weight=1250} + +TypeID = 3606 +Name = "an egg" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=30} + +TypeID = 3607 +Name = "cheese" +Flags = {Take} +Attributes = {Nutrition=9,Weight=400} + +TypeID = 3608 +Name = "a snowy dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3609 +Name = "a snowy fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3610 +Name = "a plum tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3617} + +TypeID = 3611 +Name = "a firtree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3614} + +TypeID = 3612 +Name = "a dead tree" +Flags = {Bottom,Container,Unpass,Unmove,Unlay,Disguise} +Attributes = {Capacity=4,DisguiseTarget=3634} + +TypeID = 3613 +Name = "a holy tree" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=6,LightColor=143} + +TypeID = 3614 +Name = "a fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3615 +Name = "a sycamore" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3616 +Name = "a willow" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3617 +Name = "a plum tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3618 +Name = "a red maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3619 +Name = "a pear tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3620 +Name = "a yellow maple" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3621 +Name = "a beech" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3622 +Name = "a poplar" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3623 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3624 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3625 +Name = "a dwarf tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3626 +Name = "a pine" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3627 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3628 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3629 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3630 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3631 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3632 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3633 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3634 +Name = "a dead tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3635 +Name = "old rush wood" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3636 +Name = "an old tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3637 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3638 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3639 +Name = "a palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3640 +Name = "a coconut palm" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3641 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3642 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3643 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3644 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3645 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3646 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3647 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3648 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3649 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3650 +Name = "a cactus" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3651 +Name = "wheat" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3652,TotalExpireTime=43200} + +TypeID = 3652 +Name = "wheat" +Description = "It's not mature yet" +Flags = {Unmove,Avoid,Expire} +Attributes = {ExpireTarget=3653,TotalExpireTime=43200} + +TypeID = 3653 +Name = "wheat" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3654 +Name = "moon flowers" +Flags = {Unmove} + +TypeID = 3655 +Name = "a moon flower" +Flags = {Take} +Attributes = {Weight=10} + +TypeID = 3656 +Name = "a white flower" +Flags = {Unmove} + +TypeID = 3657 +Name = "a heaven blossom" +Flags = {Unmove} + +TypeID = 3658 +Name = "a red rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3659 +Name = "a blue rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3660 +Name = "a yellow rose" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3661 +Name = "a grave flower" +Flags = {Take} +Attributes = {Weight=60} + +TypeID = 3662 +Name = "a love flower" +Flags = {Unmove} + +TypeID = 3663 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3664 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3665 +Name = "a royal blossom" +Flags = {Unmove} + +TypeID = 3666 +Name = "some sunflowers" +Flags = {Unmove,Avoid} + +TypeID = 3667 +Name = "a sunflower" +Flags = {Unmove} + +TypeID = 3668 +Name = "a tulip" +Flags = {Take} +Attributes = {Weight=50} + +TypeID = 3669 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3670 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3671 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3672 +Name = "a water lily" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3673 +Name = "an orange star" +Flags = {Take} +Attributes = {Weight=70} + +TypeID = 3674 +Name = "a goat grass" +Flags = {Take} +Attributes = {Weight=80} + +TypeID = 3675 +Name = "an orchid" +Flags = {Unmove} + +TypeID = 3676 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3677 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3678 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3679 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3680 +Name = "a rosebush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3681 +Name = "a bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3682 +Name = "a small fir tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3683 +Name = "a shadow plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3684 +Name = "a branch" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3685 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3686 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3687 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3688 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3689 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3690 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3691 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3692 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3693 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3694 +Name = "a swamp plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3695 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3696,TotalExpireTime=300} + +TypeID = 3696 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3697 +Name = "an agave" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3698 +Name = "a dry bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3699 +Name = "a blueberry bush" +Flags = {UseEvent,Bottom,Unpass,Unmove,Unlay} + +TypeID = 3700 +Name = "a blueberry bush" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=3699,TotalExpireTime=3600} + +TypeID = 3701 +Name = "jungle grass" +Flags = {Unmove,Expire} +Attributes = {ExpireTarget=3702,TotalExpireTime=300} + +TypeID = 3702 +Name = "jungle grass" +Flags = {UseEvent,Unpass,Unmove} + +TypeID = 3703 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3704 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3705 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3706 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3707 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3708 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3709 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3710 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3711 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3712 +Name = "a thorn bush" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3713 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3714 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3715 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3716 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3717 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3718 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3719 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3720 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3721 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3722 +Name = "a thorn bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3723 +Name = "a white mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=40} + +TypeID = 3724 +Name = "a red mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=4,Weight=50} + +TypeID = 3725 +Name = "a brown mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=22,Weight=20} + +TypeID = 3726 +Name = "an orange mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=30,Weight=30} + +TypeID = 3727 +Name = "a wood mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=9,Weight=30} + +TypeID = 3728 +Name = "a dark mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=6,Weight=10} + +TypeID = 3729 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=12,Weight=10} + +TypeID = 3730 +Name = "some mushrooms" +Flags = {Cumulative,Take} +Attributes = {Nutrition=3,Weight=10} + +TypeID = 3731 +Name = "a fire mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=36,Weight=10} + +TypeID = 3732 +Name = "a green mushroom" +Flags = {Cumulative,Take} +Attributes = {Nutrition=5,Weight=10} + +TypeID = 3733 +Name = "dark mushrooms" +Flags = {Unmove,Hang} + +TypeID = 3734 +Name = "a blood herb" +Flags = {Cumulative,Take} +Attributes = {Weight=120} + +TypeID = 3735 +Name = "a stone herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3736 +Name = "a star herb" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3737 +Name = "a fern" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 3738 +Name = "a sling herb" +Flags = {Cumulative,Take} +Attributes = {Weight=90} + +TypeID = 3739 +Name = "a powder herb" +Flags = {Cumulative,Take} +Attributes = {Weight=50} + +TypeID = 3740 +Name = "a shadow herb" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 3741 +Name = "a troll green" +Flags = {Cumulative,Take} +Attributes = {Weight=100} + +TypeID = 3742 +Name = "an orange tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3743 +Name = "a thread tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3744 +Name = "a jungle dweller bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3745 +Name = "a tower fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3746 +Name = "a snake nest bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3747 +Name = "a green wig bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3748 +Name = "a lizards tongue bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3749 +Name = "a jungle crown plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3750 +Name = "a green fountain bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3751 +Name = "a big fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3752 +Name = "a dragons nest tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3753 +Name = "a purple kiss bush" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3754 +Name = "a small fern" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3755 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3756 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3757 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3758 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3759 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3760 +Name = "a jungle umbrella plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3761 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3762 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3763 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3764 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3765 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3766 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3767 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3768 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3769 +Name = "a bamboo plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3770 +Name = "a bamboo plant" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3771 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3772 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3773 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3774 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3775 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3776 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3777 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3778 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3779 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3780 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3781 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3782 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3783 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3784 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3785 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3786 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3787 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3788 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3789 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3790 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3791 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3792 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3793 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3794 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3795 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3796 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3797 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3798 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3799 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3800 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3801 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3802 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3803 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3804 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3805 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3806 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3807 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3808 +Name = "jungle vines" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3809 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3810 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3811 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3812 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3813 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3814 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3815 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3816 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3817 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3818 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3819 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3820 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3821 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3822 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3823 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3824 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3825 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3826 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3827 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3828 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3829 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3830 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3831 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3832 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3833 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3834 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3835 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3836 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3837 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3838 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3839 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3840 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3841 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3842 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3843 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3844 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3845 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3846 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3847 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3848 +Name = "a liana" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3849 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3850 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3851 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3852 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3853 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3854 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3855 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3856 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3857 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3858 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3859 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3860 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3861 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3862 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3863 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3864 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3865 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3866 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3867 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3868 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3869 +Name = "a giant tree" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3870 +Name = "a chill nettle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3871 +Name = "a monkey tail" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3872 +Name = "a fairy queen" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3873 +Name = "a crane plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3874 +Name = "a jungle bells plant" +Flags = {Bottom,Unmove} + +TypeID = 3875 +Name = "a dawn singer" +Flags = {Bottom,Unmove} + +TypeID = 3876 +Name = "a turtle sprouter" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3877 +Name = "a bees ballroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3878 +Name = "a giant jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3879 +Name = "a jungle rose" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3880 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3881 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3882 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3883 +Name = "a titans orchid" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3884 +Name = "a purple cardinal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3885 +Name = "a witches cauldron plant" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3886 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3887 +Name = "a gold blossom" +Flags = {Bottom,Unmove} + +TypeID = 3888 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3889 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3890 +Name = "a meadow star" +Flags = {Bottom,Unmove} + +TypeID = 3891 +Name = "a sneeze blossom" +Flags = {Bottom,Unmove} + +TypeID = 3892 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3893 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3894 +Name = "a dew kisser flower" +Flags = {Bottom,Unmove} + +TypeID = 3895 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3896 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3897 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3898 +Name = "a velvet petal" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3899 +Name = "a devil's tongue flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3900 +Name = "a small pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3901 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3902 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3903 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3904 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3905 +Name = "a dead man's saddle" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3906 +Name = "a dead man's saddle" +Flags = {Bottom,Unmove} + +TypeID = 3907 +Name = "dead man's saddles" +Flags = {Bottom,Unmove} + +TypeID = 3908 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3909 +Name = "a moss cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3910 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3911 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove} + +TypeID = 3912 +Name = "a slime table mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3913 +Name = "slime table mushrooms" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3914 +Name = "a giggle mushroom" +Flags = {Bottom,Unmove} + +TypeID = 3915 +Name = "giggle mushrooms" +Flags = {Bottom,Unmove} + +TypeID = 3916 +Name = "a cat's food mushroom" +Flags = {Bottom,Unmove} + +TypeID = 3917 +Name = "cat's food mushrooms" +Flags = {Bottom,Unmove} + +TypeID = 3918 +Name = "a glimmer cap mushroom" +Flags = {Bottom,Unmove} + +TypeID = 3919 +Name = "glimmer cap mushrooms" +Flags = {Bottom,Unmove} + +TypeID = 3920 +Name = "a giant glimmer cap mushroom" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3921 +Name = "a large pearl flower" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3922 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3923 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3924 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3925 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3926 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3927 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3928 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3929 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3930 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3931 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3932 +Name = "a fallen tree" +Flags = {Bottom,Unmove} + +TypeID = 3933 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3934 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3935 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3936 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3937 +Name = "a fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3938 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3939 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3940 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3941 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3942 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3943 +Name = "jungle vines" +Flags = {Unmove} + +TypeID = 3944 +Name = "a jungle maw" +Flags = {Bottom,CollisionEvent,Unmove} + +TypeID = 3945 +Name = "a jungle maw" +Flags = {Bottom,Unmove,Expire} +Attributes = {ExpireTarget=3944,TotalExpireTime=150} + +TypeID = 3946 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3947 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3948 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3949 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3950 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3951 +Name = "a fallen tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3952 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3953 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3954 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3955 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3956 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3957 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3958 +Name = "a giant tree" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 3959 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3960 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3961 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3962 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3963 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3964 +Name = "a giant tree root" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 3965 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3966 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3967 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3968 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3969 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3970 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3971 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3972 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3973 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3974 +Name = "a giant tree root" +Flags = {Unmove} + +TypeID = 3975 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3976 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3977 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3978 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3979 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3980 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3981 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3982 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3983 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3984 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3985 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3986 +Name = "a mossy fallen tree" +Flags = {Bottom,Unmove,Height} + +TypeID = 3987 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=60000,ExpireTarget=3991,TotalExpireTime=1800} + +TypeID = 3988 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=4600,ExpireTarget=4003,TotalExpireTime=1200} + +TypeID = 3989 +Name = "a dead cyclops" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4092,TotalExpireTime=1800} + +TypeID = 3990 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=60000,ExpireTarget=4103,TotalExpireTime=1200} + +TypeID = 3991 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 3992 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 3993 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3994 +Name = "a dead rat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=3995,TotalExpireTime=1200} + +TypeID = 3995 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=3996,TotalExpireTime=1200} + +TypeID = 3996 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 3997 +Name = "a dead rat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 3998 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=3999,TotalExpireTime=1200} + +TypeID = 3999 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=840,ExpireTarget=4000,TotalExpireTime=1200} + +TypeID = 4000 +Name = "a dead snake" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=620,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4001 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=82000,ExpireTarget=4002,TotalExpireTime=1800} + +TypeID = 4002 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=65000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4003 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=4004,TotalExpireTime=1200} + +TypeID = 4004 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1200,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4005 +Name = "a dead rotworm" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4006,TotalExpireTime=1200} + +TypeID = 4006 +Name = "a dead rotworm" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4145,TotalExpireTime=1200} + +TypeID = 4007 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=26000,ExpireTarget=4008,TotalExpireTime=1800} + +TypeID = 4008 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=20000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4009 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=13000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4010 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4011 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=130000,ExpireTarget=4012,TotalExpireTime=1800} + +TypeID = 4012 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=105000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4013 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=85000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4014 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4015 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4016 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=68000,ExpireTarget=4017,TotalExpireTime=1800} + +TypeID = 4017 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=58000,ExpireTarget=4018,TotalExpireTime=1800} + +TypeID = 4018 +Name = "a dead deer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4019 +Name = "a dead deer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=10000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4020 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=21000,ExpireTarget=4021,TotalExpireTime=1200} + +TypeID = 4021 +Name = "a dead dog" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=14000,ExpireTarget=4022,TotalExpireTime=1200} + +TypeID = 4022 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=9000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4023 +Name = "a dead dog" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=1000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4024 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=40000,ExpireTarget=4156,TotalExpireTime=1200} + +TypeID = 4025 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4026,TotalExpireTime=1800} + +TypeID = 4026 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4027 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4028 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4029 +Name = "a dead spider" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4066,TotalExpireTime=1200} + +TypeID = 4030 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4031,TotalExpireTime=1800} + +TypeID = 4031 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4032,TotalExpireTime=1800} + +TypeID = 4032 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4033 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4034 +Name = "a slain ghoul" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=64000,ExpireTarget=4035,TotalExpireTime=1800} + +TypeID = 4035 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=57000,ExpireTarget=4036,TotalExpireTime=1800} + +TypeID = 4036 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4037 +Name = "a slain ghoul" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4038 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4039,TotalExpireTime=1800} + +TypeID = 4039 +Name = "a dead giant spider" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4040,TotalExpireTime=1800} + +TypeID = 4040 +Name = "a dead giant spider" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4041 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4042,TotalExpireTime=1800} + +TypeID = 4042 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4043 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4044,TotalExpireTime=1800} + +TypeID = 4044 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4045 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4046,TotalExpireTime=1800} + +TypeID = 4046 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4047 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4048,TotalExpireTime=1800} + +TypeID = 4048 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4049 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4050 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4051 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4052 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=140000,ExpireTarget=4053,TotalExpireTime=1800} + +TypeID = 4053 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4054 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4055 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4056 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4057 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=150000,ExpireTarget=4058,TotalExpireTime=1800} + +TypeID = 4058 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=110000,ExpireTarget=4013,TotalExpireTime=1800} + +TypeID = 4059 +Name = "a dead minotaur" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=2,Weight=80000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4060 +Name = "a dead minotaur" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4061 +Name = "a pile of bones" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4062 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4063,TotalExpireTime=1800} + +TypeID = 4063 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4027,TotalExpireTime=1800} + +TypeID = 4064 +Name = "a dead dragon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4065 +Name = "a dead dragon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4066 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7000,ExpireTarget=4125,TotalExpireTime=1200} + +TypeID = 4067 +Name = "a dead fire devil" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=80000,ExpireTarget=4068,TotalExpireTime=1800} + +TypeID = 4068 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=60000,ExpireTarget=4069,TotalExpireTime=1800} + +TypeID = 4069 +Name = "a dead fire devil" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4070 +Name = "a dead lion" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4071,TotalExpireTime=1800} + +TypeID = 4071 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4072,TotalExpireTime=1800} + +TypeID = 4072 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4073 +Name = "a dead lion" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4074 +Name = "a dead bear" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4075,TotalExpireTime=1800} + +TypeID = 4075 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4076,TotalExpireTime=1800} + +TypeID = 4076 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4077 +Name = "a dead bear" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4078 +Name = "a dead scorpion" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4079,TotalExpireTime=1200} + +TypeID = 4079 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4146,TotalExpireTime=1200} + +TypeID = 4080 +Name = "a dead wasp" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=9000,ExpireTarget=4081,TotalExpireTime=1200} + +TypeID = 4081 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6000,ExpireTarget=4082,TotalExpireTime=1200} + +TypeID = 4082 +Name = "a dead wasp" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4083 +Name = "a dead bug" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000,ExpireTarget=4084,TotalExpireTime=1200} + +TypeID = 4084 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=6500,ExpireTarget=4085,TotalExpireTime=1200} + +TypeID = 4085 +Name = "a dead bug" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4086 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4087,TotalExpireTime=1800} + +TypeID = 4087 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4088 +Name = "a dead sheep" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=35000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4089 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4090,TotalExpireTime=1800} + +TypeID = 4090 +Name = "a dead beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=SLIME,ExpireTarget=4091,TotalExpireTime=1800} + +TypeID = 4091 +Name = "a dead beholder" +Flags = {Corpse,Expire} +Attributes = {FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4092 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4093,TotalExpireTime=1800} + +TypeID = 4093 +Name = "a dead cyclops" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4094 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=2200,ExpireTarget=4158,TotalExpireTime=1200} + +TypeID = 4095 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=70000,ExpireTarget=4096,TotalExpireTime=1800} + +TypeID = 4096 +Name = "a dead sheep" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4088,TotalExpireTime=1800} + +TypeID = 4097 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4098,TotalExpireTime=1800} + +TypeID = 4098 +Name = "a slain demon" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4099,TotalExpireTime=1800} + +TypeID = 4099 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4100 +Name = "a slain demon" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4101 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000,ExpireTarget=4102,TotalExpireTime=1800} + +TypeID = 4102 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=70000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4103 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=40000,ExpireTarget=4104,TotalExpireTime=1200} + +TypeID = 4104 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4105 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=45000,ExpireTarget=4106,TotalExpireTime=1800} + +TypeID = 4106 +Name = "a dead wolf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=32000,ExpireTarget=4009,TotalExpireTime=1800} + +TypeID = 4107 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=18000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4108 +Name = "a dead wolf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4109 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=92000,ExpireTarget=4110,TotalExpireTime=1800} + +TypeID = 4110 +Name = "a dead troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=83000,ExpireTarget=3992,TotalExpireTime=1800} + +TypeID = 4111 +Name = "a dead troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=69000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4112 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4113,TotalExpireTime=1800} + +TypeID = 4113 +Name = "a dead behemoth" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4114,TotalExpireTime=1800} + +TypeID = 4114 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4115 +Name = "a dead behemoth" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4116 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=75000,ExpireTarget=4117,TotalExpireTime=1800} + +TypeID = 4117 +Name = "a dead pig" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,Weight=60000,ExpireTarget=4118,TotalExpireTime=1800} + +TypeID = 4118 +Name = "a dead pig" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4119 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4120,TotalExpireTime=1800} + +TypeID = 4120 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=90000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4121 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=68000,ExpireTarget=4122,TotalExpireTime=1800} + +TypeID = 4122 +Name = "a dead goblin" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=58000,ExpireTarget=4123,TotalExpireTime=1800} + +TypeID = 4123 +Name = "a dead goblin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=37000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4124 +Name = "a dead golin" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=28000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4125 +Name = "a dead spider" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4126 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4127,TotalExpireTime=1800} + +TypeID = 4127 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4128 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=39000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4129 +Name = "a dead elf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=27000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4130 +Name = "remains of a mummy" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=48000,ExpireTarget=4131,TotalExpireTime=1200} + +TypeID = 4131 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=31000,ExpireTarget=4132,TotalExpireTime=1200} + +TypeID = 4132 +Name = "remains of a mummy" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=12000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4133 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4134,TotalExpireTime=1800} + +TypeID = 4134 +Name = "a split stone golem" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4135,TotalExpireTime=1800} + +TypeID = 4135 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4136 +Name = "a split stone golem" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4137 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=78000,ExpireTarget=4138,TotalExpireTime=1200} + +TypeID = 4138 +Name = "a slain vampire" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=69000,ExpireTarget=4139,TotalExpireTime=1200} + +TypeID = 4139 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=48000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4140 +Name = "a slain vampire" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=33000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4141 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4142,TotalExpireTime=1800} + +TypeID = 4142 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4143 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=52000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4144 +Name = "a dead dwarf" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=34000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4145 +Name = "a dead rotworm" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4146 +Name = "a dead scorpion" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4147 +Name = "a dead orc" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=51000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4148 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=110000,ExpireTarget=4149,TotalExpireTime=1800} + +TypeID = 4149 +Name = "a dead orc" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=92000,ExpireTarget=4147,TotalExpireTime=1800} + +TypeID = 4150 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4151,TotalExpireTime=1800} + +TypeID = 4151 +Name = "a dead war wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4152,TotalExpireTime=1800} + +TypeID = 4152 +Name = "a dead war wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4153 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4154,TotalExpireTime=1800} + +TypeID = 4154 +Name = "a dead orc and wolf" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4155,TotalExpireTime=1800} + +TypeID = 4155 +Name = "a dead orc and wolf" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4156 +Name = "a slain skeleton" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=26000,ExpireTarget=4157,TotalExpireTime=1200} + +TypeID = 4157 +Name = "a slain skeleton" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=14000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4158 +Name = "remains of a ghost" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=1300,ExpireTarget=4159,TotalExpireTime=1200} + +TypeID = 4159 +Name = "remains of a ghost" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=900,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4160 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4161,TotalExpireTime=1800} + +TypeID = 4161 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4162 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=71000,ExpireTarget=4163,TotalExpireTime=1800} + +TypeID = 4163 +Name = "a dead elf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=65000,ExpireTarget=4128,TotalExpireTime=1800} + +TypeID = 4164 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4165,TotalExpireTime=1800} + +TypeID = 4165 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4166 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4167,TotalExpireTime=1800} + +TypeID = 4167 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4168 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000,ExpireTarget=4169,TotalExpireTime=1800} + +TypeID = 4169 +Name = "a dead dwarf" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=10,Weight=71000,ExpireTarget=4143,TotalExpireTime=1800} + +TypeID = 4170 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4171,TotalExpireTime=1800} + +TypeID = 4171 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4172,TotalExpireTime=1800} + +TypeID = 4172 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4173 +Name = "a dead rabbit" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4174,TotalExpireTime=1200} + +TypeID = 4174 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4175,TotalExpireTime=1200} + +TypeID = 4175 +Name = "a dead rabbit" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4176 +Name = "a dead swamp troll" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=SLIME,Weight=60000,ExpireTarget=4177,TotalExpireTime=1800} + +TypeID = 4177 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=4178,TotalExpireTime=1800} + +TypeID = 4178 +Name = "a dead swamp troll" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=30000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4179 +Name = "a slain banshee" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,Weight=11000,ExpireTarget=4180,TotalExpireTime=1800} + +TypeID = 4180 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=4181,TotalExpireTime=1800} + +TypeID = 4181 +Name = "a slain banshee" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4182 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=BLOOD,ExpireTarget=4183,TotalExpireTime=1800} + +TypeID = 4183 +Name = "a dead djinn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4184,TotalExpireTime=1800} + +TypeID = 4184 +Name = "a dead djinn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4185 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,FluidSource=SLIME,ExpireTarget=4186,TotalExpireTime=1800} + +TypeID = 4186 +Name = "a dead scarab" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=6,ExpireTarget=4187,TotalExpireTime=1800} + +TypeID = 4187 +Name = "a dead scarab" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4188 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {FluidSource=BLOOD,Weight=1320,ExpireTarget=4189,TotalExpireTime=1200} + +TypeID = 4189 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=920,ExpireTarget=4190,TotalExpireTime=1200} + +TypeID = 4190 +Name = "a dead cobra" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=680,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4191 +Name = "a dead larva" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=1050,ExpireTarget=4192,TotalExpireTime=1200} + +TypeID = 4192 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=820,ExpireTarget=4193,TotalExpireTime=1200} + +TypeID = 4193 +Name = "a dead larva" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=530,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4194 +Name = "a dead scarab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=12000,ExpireTarget=4195,TotalExpireTime=1200} + +TypeID = 4195 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=7800,ExpireTarget=4196,TotalExpireTime=1200} + +TypeID = 4196 +Name = "a dead scarab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3600,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4197 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4198,TotalExpireTime=1800} + +TypeID = 4198 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4199,TotalExpireTime=1800} + +TypeID = 4199 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4200 +Name = "a dead hyaena" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4201,TotalExpireTime=1800} + +TypeID = 4201 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4202,TotalExpireTime=1800} + +TypeID = 4202 +Name = "a dead hyaena" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4203 +Name = "a dead gargoyle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4204,TotalExpireTime=1800} + +TypeID = 4204 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4205,TotalExpireTime=1800} + +TypeID = 4205 +Name = "a dead gargoyle" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4206 +Name = "a slain lich" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4207,TotalExpireTime=1800} + +TypeID = 4207 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4208,TotalExpireTime=1800} + +TypeID = 4208 +Name = "a slain lich" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4209 +Name = "a slain crypt shambler" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,Weight=31000,ExpireTarget=4210,TotalExpireTime=1800} + +TypeID = 4210 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20000,ExpireTarget=4211,TotalExpireTime=1800} + +TypeID = 4211 +Name = "a slain crypt shambler" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=8000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4212 +Name = "a slain bonebeast" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,ExpireTarget=4213,TotalExpireTime=1800} + +TypeID = 4213 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4214,TotalExpireTime=1800} + +TypeID = 4214 +Name = "a slain bonebeast" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4215 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4216,TotalExpireTime=1800} + +TypeID = 4216 +Name = "a dead pharaoh" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4217,TotalExpireTime=1800} + +TypeID = 4217 +Name = "a dead pharaoh" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4218 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4219,TotalExpireTime=1800} + +TypeID = 4219 +Name = "a dead efreet" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4220,TotalExpireTime=1800} + +TypeID = 4220 +Name = "a dead efreet" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4221 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4222,TotalExpireTime=1800} + +TypeID = 4222 +Name = "a dead marid" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=10,ExpireTarget=4223,TotalExpireTime=1800} + +TypeID = 4223 +Name = "a dead marid" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4224 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5900,ExpireTarget=4225,TotalExpireTime=1800} + +TypeID = 4225 +Name = "a dead badger" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5500,ExpireTarget=4226,TotalExpireTime=1800} + +TypeID = 4226 +Name = "a dead badger" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4227 +Name = "a dead skunk" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5600,ExpireTarget=4228,TotalExpireTime=1800} + +TypeID = 4228 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=4229,TotalExpireTime=1800} + +TypeID = 4229 +Name = "a dead skunk" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2500,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4230 +Name = "a dead gazer" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=5400,ExpireTarget=4231,TotalExpireTime=1800} + +TypeID = 4231 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3200,ExpireTarget=4232,TotalExpireTime=1800} + +TypeID = 4232 +Name = "a dead gazer" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=2000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4233 +Name = "a dead elder beholder" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4234,TotalExpireTime=1800} + +TypeID = 4234 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4235,TotalExpireTime=1800} + +TypeID = 4235 +Name = "a dead elder beholder" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4236 +Name = "a dead yeti" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4237,TotalExpireTime=1800} + +TypeID = 4237 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4238,TotalExpireTime=1800} + +TypeID = 4238 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4239 +Name = "a dead yeti" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4240 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4241,TotalExpireTime=1800} + +TypeID = 4241 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4242 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=50000,ExpireTarget=0,TotalExpireTime=1800} + +TypeID = 4243 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4244 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4245 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4246 +Name = "a dead human" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=5000,ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4247 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4248,TotalExpireTime=1800} + +TypeID = 4248 +Name = "a dead human" +Flags = {Container,Corpse,Expire,AllowDistRead} +Attributes = {Capacity=10,ExpireTarget=4242,TotalExpireTime=1800} + +TypeID = 4249 +Name = "a dead troll" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=80000} + +TypeID = 4250 +Name = "a dead spider" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=SLIME,Weight=10000} + +TypeID = 4251 +Name = "a dead cyclops" +Flags = {Container} +Attributes = {Capacity=12,FluidSource=BLOOD} + +TypeID = 4252 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4253 +Name = "a dead troll" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4254 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4255 +Name = "a dead rat" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4256 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4257 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4258 +Name = "a dead rat" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4259 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4260 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=650} + +TypeID = 4261 +Name = "a dead snake" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4262 +Name = "a dead orc" +Flags = {Container,Take} +Attributes = {Capacity=10,FluidSource=BLOOD,Weight=90000} + +TypeID = 4263 +Name = "a dead orc" +Flags = {Take} +Attributes = {Weight=60000} + +TypeID = 4264 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4265 +Name = "a dead spider" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4266 +Name = "a dead rotworm" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4267 +Name = "a dead rotworm" + +TypeID = 4268 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=6,FluidSource=BLOOD,Weight=21000} + +TypeID = 4269 +Name = "a dead wolf" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=15000} + +TypeID = 4270 +Name = "a dead wolf" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4271 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4272 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=12,FluidSource=BLOOD,Weight=150000} + +TypeID = 4273 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=7,Weight=110000} + +TypeID = 4274 +Name = "a dead minotaur" +Flags = {Container,Take} +Attributes = {Capacity=2,Weight=80000} + +TypeID = 4275 +Name = "a dead minotaur" +Flags = {Take} +Attributes = {Weight=40000} + +TypeID = 4276 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=20000} + +TypeID = 4277 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=60000} + +TypeID = 4278 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=3,Weight=50000} + +TypeID = 4279 +Name = "a dead deer" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=30000} + +TypeID = 4280 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4281 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=2,FluidSource=BLOOD,Weight=20000} + +TypeID = 4282 +Name = "a dead dog" +Flags = {Container,Take} +Attributes = {Capacity=1,Weight=10000} + +TypeID = 4283 +Name = "a dead dog" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4284 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=1000} + +TypeID = 4285 +Name = "a pile of bones" +Flags = {Container,Take} +Attributes = {Capacity=10,Weight=10000} + +TypeID = 4286 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=16,FluidSource=BLOOD} + +TypeID = 4287 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4288 +Name = "a dead dragon" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4289 +Name = "a pile of bones" + +TypeID = 4290 +Name = "remains of a ghost" +Flags = {Container,Take} +Attributes = {Capacity=8,Weight=2200} + +TypeID = 4291 +Name = "a dead bear" +Flags = {Container} +Attributes = {Capacity=8,FluidSource=BLOOD} + +TypeID = 4292 +Name = "a dead bear" + +TypeID = 4293 +Name = "a dead bear" + +TypeID = 4294 +Name = "a pile of bones" + +TypeID = 4295 +Name = "a slain ghoul" +Flags = {Container,Take} +Attributes = {Capacity=4,Weight=40000} + +TypeID = 4296 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4297 +Name = "a slain ghoul" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4298 +Name = "a pile of bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4299 +Name = "a dead cyclops" + +TypeID = 4300 +Name = "a pile of bones" + +TypeID = 4301 +Name = "a dead rabbit" +Flags = {Container,Take} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300} + +TypeID = 4302 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=4400} + +TypeID = 4303 +Name = "a dead rabbit" +Flags = {Take} +Attributes = {Weight=3000} + +TypeID = 4304 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4305 +Name = "a pile of bones" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4306 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4307 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4308 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4309 +Name = "a pile of bones" +Flags = {Unmove} + +TypeID = 4310 +Name = "a dead elephant" +Flags = {Container} +Attributes = {Capacity=15} + +TypeID = 4311 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=10} + +TypeID = 4312 +Name = "a dead human" +Flags = {Container} +Attributes = {Capacity=5} + +TypeID = 4313 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=50000} + +TypeID = 4314 +Name = "a corpse" +Flags = {Take} +Attributes = {Weight=30000} + +TypeID = 4315 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=15000} + +TypeID = 4316 +Name = "a skeleton" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4317 +Name = "some bones" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4318 +Name = "a dead crab" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4319,TotalExpireTime=1200} + +TypeID = 4319 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4320,TotalExpireTime=1200} + +TypeID = 4320 +Name = "a dead crab" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4321 +Name = "a dead lizard templar" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4322,TotalExpireTime=1200} + +TypeID = 4322 +Name = "a dead lizard templar" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4323,TotalExpireTime=1200} + +TypeID = 4323 +Name = "a dead lizard templar" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4324 +Name = "a dead lizard sentinel" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4325,TotalExpireTime=1200} + +TypeID = 4325 +Name = "a dead lizard sentinel" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4326,TotalExpireTime=1200} + +TypeID = 4326 +Name = "a dead lizard sentinel" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4327 +Name = "a dead lizard snakecharmer" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4328,TotalExpireTime=1200} + +TypeID = 4328 +Name = "a dead lizard snakecharmer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4329,TotalExpireTime=1200} + +TypeID = 4329 +Name = "a dead lizard snakecharmer" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4330 +Name = "a dead chicken" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4331,TotalExpireTime=1200} + +TypeID = 4331 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4332,TotalExpireTime=1200} + +TypeID = 4332 +Name = "a dead chicken" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4333 +Name = "a dead kongra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4334,TotalExpireTime=1200} + +TypeID = 4334 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4335,TotalExpireTime=1200} + +TypeID = 4335 +Name = "a dead kongra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4336 +Name = "a dead merlkin" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4337,TotalExpireTime=1200} + +TypeID = 4337 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4338,TotalExpireTime=1200} + +TypeID = 4338 +Name = "a dead merlkin" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4339 +Name = "a dead sibang" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4340,TotalExpireTime=1200} + +TypeID = 4340 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4341,TotalExpireTime=1200} + +TypeID = 4341 +Name = "a dead sibang" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4342 +Name = "a dead crocodile" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4343,TotalExpireTime=1200} + +TypeID = 4343 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4344,TotalExpireTime=1200} + +TypeID = 4344 +Name = "a dead crocodile" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4345 +Name = "a dead carniphila" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4346,TotalExpireTime=1200} + +TypeID = 4346 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4347,TotalExpireTime=1200} + +TypeID = 4347 +Name = "a dead carniphila" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4348 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4349,TotalExpireTime=1800} + +TypeID = 4349 +Name = "a dead hydra" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4350,TotalExpireTime=1800} + +TypeID = 4350 +Name = "a dead hydra" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4351 +Name = "a dead panda" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4352,TotalExpireTime=1200} + +TypeID = 4352 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4353,TotalExpireTime=1200} + +TypeID = 4353 +Name = "a dead panda" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4354 +Name = "a dead centipede" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4355,TotalExpireTime=1200} + +TypeID = 4355 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4356,TotalExpireTime=1200} + +TypeID = 4356 +Name = "a dead centipede" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4357 +Name = "a dead tiger" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4358,TotalExpireTime=1200} + +TypeID = 4358 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4359,TotalExpireTime=1200} + +TypeID = 4359 +Name = "a dead tiger" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4360 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4361,TotalExpireTime=1800} + +TypeID = 4361 +Name = "a dead elephant" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4362,TotalExpireTime=1800} + +TypeID = 4362 +Name = "a dead elephant" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4363 +Name = "a dead bat" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4364,TotalExpireTime=1200} + +TypeID = 4364 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4365,TotalExpireTime=1200} + +TypeID = 4365 +Name = "a dead bat" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4366 +Name = "a dead flamingo" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4367,TotalExpireTime=1200} + +TypeID = 4367 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4368,TotalExpireTime=1200} + +TypeID = 4368 +Name = "a dead flamingo" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4369 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4370,TotalExpireTime=1200} + +TypeID = 4370 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4371,TotalExpireTime=1200} + +TypeID = 4371 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4372 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4373,TotalExpireTime=1200} + +TypeID = 4373 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4374,TotalExpireTime=1200} + +TypeID = 4374 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4375 +Name = "a dead dworc" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4376,TotalExpireTime=1200} + +TypeID = 4376 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4377,TotalExpireTime=1200} + +TypeID = 4377 +Name = "a dead dworc" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4378 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4379 +Name = "a dead parrot" +Flags = {Container,Take,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,Weight=6300,ExpireTarget=4380,TotalExpireTime=1200} + +TypeID = 4380 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=4400,ExpireTarget=4381,TotalExpireTime=1200} + +TypeID = 4381 +Name = "a dead parrot" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=3000,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4382 +Name = "a dead bird" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=7,FluidSource=BLOOD,ExpireTarget=4383,TotalExpireTime=1200} + +TypeID = 4383 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4384,TotalExpireTime=1200} + +TypeID = 4384 +Name = "a dead bird" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4385 +Name = "a dead tarantula" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=4386,TotalExpireTime=1200} + +TypeID = 4386 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=4387,TotalExpireTime=1200} + +TypeID = 4387 +Name = "a dead tarantula" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4388 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,FluidSource=BLOOD,ExpireTarget=4389,TotalExpireTime=1800} + +TypeID = 4389 +Name = "a dead serpent spawn" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=15,ExpireTarget=4390,TotalExpireTime=1800} + +TypeID = 4390 +Name = "a dead serpent spawn" +Flags = {Corpse,Expire} +Attributes = {ExpireTarget=0,TotalExpireTime=10} + +TypeID = 4391 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=BLOOD,ExpireTarget=4392,TotalExpireTime=1200} + +TypeID = 4392 +Name = "a lifeless nettle" +Flags = {Container,Corpse,Expire} +Attributes = {Capacity=5,FluidSource=SLIME,ExpireTarget=0,TotalExpireTime=1200} + +TypeID = 4393 +Name = "a drawbridge" +Flags = {Bank,Unmove,Avoid,Disguise} +Attributes = {Waypoints=90,DisguiseTarget=1771} + +TypeID = 4394 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4395 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4396 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4397 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4398 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4399 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4400 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4401 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4402 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4403 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4404 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4405 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4406 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4407 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4408 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4409 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4410 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4411 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4412 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4413 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4414 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4415 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4416 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4417 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4418 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4419 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4420 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4421 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4422 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4423 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4424 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4425 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4426 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4427 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4428 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4429 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4430 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4431 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4432 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4433 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4434 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4435 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4436 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4437 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4438 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4439 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4440 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4441 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4442 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4443 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4444 +Name = "a mountain" +Flags = {Bank,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Waypoints=0} + +TypeID = 4445 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4446 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4447 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4448 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4449 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4450 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4451 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4452 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4453 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4454 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4455 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4456 +Name = "rocks" +Flags = {Clip,Unmove} + +TypeID = 4457 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4458 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4459 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4460 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4461 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4462 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4463 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4464 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4465 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4466 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4467 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4468 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4469 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4470 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4471 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4472 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4473 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4474 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4475 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4476 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4477 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4478 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4479 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4480 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4481 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4482 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4483 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4484 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4485 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4486 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4487 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4488 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4489 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4490 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4491 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4492 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4493 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4494 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4495 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4496 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4497 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4498 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4499 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4500 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4501 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4502 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4503 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4504 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4505 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4506 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4507 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4508 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4509 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4510 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4511 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4512 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4513 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4514 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4515 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4516 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4517 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4518 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4519 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4520 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4521 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4522 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4523 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4524 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4525 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4526 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4527 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4528 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4529 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4530 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4531 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4532 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4533 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4534 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4535 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4536 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4537 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4538 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4539 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4540 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4541 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4542 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4543 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4544 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4545 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4546 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4547 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4548 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4549 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4550 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4551 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4552 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4553 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4554 +Name = "gravel" +Flags = {Clip,Unmove} + +TypeID = 4555 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4556 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4557 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4558 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4559 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4560 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4561 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4562 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4563 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4564 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4565 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4566 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4567 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4568 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4569 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4570 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4571 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4572 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4573 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4574 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4575 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4576 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4577 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4578 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4579 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4580 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4581 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4582 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4583 +Name = "rock soil" +Flags = {Clip,Unmove} + +TypeID = 4584 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4585 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4586 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4587 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4588 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4589 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4590 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4591 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4592 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4593 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4594 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4595 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4596 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4597 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4598 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4599 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4600 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4601 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4602 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4603 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4604 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4605 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4606 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4607 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4608 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0,FluidSource=WATER} + +TypeID = 4609 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4597,TotalExpireTime=2200} + +TypeID = 4610 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4598,TotalExpireTime=2200} + +TypeID = 4611 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4599,TotalExpireTime=2200} + +TypeID = 4612 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4600,TotalExpireTime=2200} + +TypeID = 4613 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4601,TotalExpireTime=2200} + +TypeID = 4614 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Expire} +Attributes = {Waypoints=0,FluidSource=WATER,ExpireTarget=4602,TotalExpireTime=2200} + +TypeID = 4615 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4609} + +TypeID = 4616 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4610} + +TypeID = 4617 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4611} + +TypeID = 4618 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4612} + +TypeID = 4619 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4613} + +TypeID = 4620 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove,Disguise} +Attributes = {Waypoints=0,FluidSource=WATER,DisguiseTarget=4614} + +TypeID = 4621 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4622 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4623 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4624 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4625 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4626 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4627 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4628 +Name = "shallow water" +Flags = {Bank,Unpass,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4629 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4630 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4631 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4632 +Name = "shallow water" +Flags = {Bank,Unmove} +Attributes = {Waypoints=160,FluidSource=WATER} + +TypeID = 4633 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4634 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4635 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4636 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4637 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4638 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4639 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4640 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4641 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4642 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4643 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4644 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4645 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4646 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4647 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4648 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4649 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4650 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4651 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4652 +Name = "shallow water" +Flags = {Clip,Unpass,Unmove} +Attributes = {FluidSource=WATER} + +TypeID = 4653 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4654 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4655 +Name = "shallow water" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=1,FluidSource=WATER} + +TypeID = 4656 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4657 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4658 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4659 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4660 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4661 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4662 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4663 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4664 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4665 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4666 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4667 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4668 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4669 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4670 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4671 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4672 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4673 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4674 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4675 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4676 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4677 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4678 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4679 +Name = "dirt" +Flags = {Clip,Unmove} + +TypeID = 4680 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4681 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4682 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4683 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4684 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4685 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4686 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4687 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4688 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4689 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4690 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4691 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4692 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4693 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4694 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4695 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4696 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4697 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4698 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4699 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4700 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4701 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4702 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4703 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4704 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4705 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4706 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4707 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4708 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4709 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4710 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4711 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4712 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4713 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4714 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4715 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4716 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4717 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4718 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4719 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4720 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4721 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4722 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4723 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4724 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4725 +Name = "swamp" +Flags = {Clip,Unmove} + +TypeID = 4726 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4727 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4728 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4729 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4730 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4731 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4732 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4733 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4734 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4735 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4736 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4737 +Name = "snow" +Flags = {Clip,Unmove} + +TypeID = 4738 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4739 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4740 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4741 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4742 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4743 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4744 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4745 +Name = "grass" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4746 +Name = "rock soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=120} + +TypeID = 4747 +Name = "swamp" +Flags = {Bank,CollisionEvent,Unpass,Unmove} +Attributes = {Waypoints=0} + +TypeID = 4748 +Name = "gravel" +Flags = {Bank,Unmove} +Attributes = {Waypoints=150} + +TypeID = 4749 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4750 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4751 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4752 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4753 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4754 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4755 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4756 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4757 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4758 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4759 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4760 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4761 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4762 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4763 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4764 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4765 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4766 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4767 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4768 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4769 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4770 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4771 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4772 +Name = "sand" +Flags = {Clip,Unmove} + +TypeID = 4773 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4774 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4775 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4776 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4777 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4778 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4779 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4780 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4781 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4782 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4783 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4784 +Name = "grass" +Flags = {Clip,Unmove} + +TypeID = 4785 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4786 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4787 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4788 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4789 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4790 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4791 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4792 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4793 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4794 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4795 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4796 +Name = "dirt floor" +Flags = {Clip,Unmove} + +TypeID = 4797 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4798 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4799 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4800 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4801 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4802 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4803 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4804 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4805 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4806 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4807 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4808 +Name = "lava" +Flags = {Clip,Unmove} + +TypeID = 4809 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4810 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4811 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4812 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4813 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4814 +Name = "shallow water" +Description = "You see the silvery movement of fish" +Flags = {Bank,Unmove} +Attributes = {Waypoints=170} + +TypeID = 4815 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4816 +Name = "a mountain" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4817 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4818 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4819 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4820 +Name = "shallow water" +Flags = {Clip,Unmove} + +TypeID = 4821 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4822 +Name = "ploughed soil" +Flags = {Bank,Unmove} +Attributes = {Waypoints=180} + +TypeID = 4823 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4824 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4825 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4826 +Name = "stairs" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=100} + +TypeID = 4827 +Name = "whisper moss" +Flags = {Cumulative,Take} +Attributes = {Weight=20} + +TypeID = 4828 +Name = "a flask of cough syrup" +Description = "It smells like herbs" +Flags = {Take} +Attributes = {Weight=300} + +TypeID = 4829 +Name = "a witches cap mushroom" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4830 +Name = "witches mushrooms" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay,Disguise} +Attributes = {DisguiseTarget=3919} + +TypeID = 4831 +Name = "an old parchment" +Description = "It is covered with foreign symbols" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4832 +Name = "a giant ape's hair" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4833 +Name = "a giant footprint" +Flags = {Bottom,Chest,Unmove,Disguise} +Attributes = {DisguiseTarget=2753} + +TypeID = 4834 +Name = "a family brooch" +Description = "The emblem of a dwarven family is engraved on it" +Flags = {Take} +Attributes = {Weight=110} + +TypeID = 4835 +Name = "a snake destroyer" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=6600} + +TypeID = 4836 +Name = "a spectral dress" +Flags = {Take} +Attributes = {Weight=1000,SlotType=BODY} + +TypeID = 4837 +Name = "an icicle" +Description = "It is melting rapidly" +Flags = {Take,Expire} +Attributes = {Weight=1900,ExpireTarget=0,TotalExpireTime=600} + +TypeID = 4838 +Name = "strange powder" +Flags = {Cumulative,Take} +Attributes = {Weight=500} + +TypeID = 4839 +Name = "a hydra egg" +Flags = {Take} +Attributes = {Weight=5000} + +TypeID = 4840 +Name = "a spectral stone" +Description = "It is pulsating with spectral energy" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=250,Brightness=2,LightColor=29} + +TypeID = 4841 +Name = "a memory stone" +Flags = {Take} +Attributes = {Weight=250} + +TypeID = 4842 +Name = "a sheet of tracing paper" +Description = "It is blank" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=100} + +TypeID = 4843 +Name = "a sheet of tracing paper" +Description = "It contains some strange symbols of the lizard language" +Flags = {Take} +Attributes = {Weight=100} + +TypeID = 4844 +Name = "an elven poetry book" +Description = "It contains a collection of beautiful elven poems" +Flags = {Take} +Attributes = {Weight=1300} + +TypeID = 4845 +Name = "a dwarven pickaxe" +Description = "It is a masterpiece of dwarvish smithery and made of especially hard steel" +Flags = {Take} +Attributes = {Weight=6000} + +TypeID = 4846 +Name = "a wrinkled parchment" +Description = "It is covered with strange numbers" +Flags = {Take} +Attributes = {Weight=200} + +TypeID = 4847 +Name = "a funeral urn" +Description = "It contains the ashes of a lizard high priest" +Flags = {Take} +Attributes = {Weight=10000} + +TypeID = 4848 +Name = "a small cask" +Description = "It is filled with the blood of the snake god" +Flags = {Unpass,Unmove,Unlay} +Attributes = {FluidSource=BLOOD} + +TypeID = 4849 +Name = "wooden trash" +Description = "The blood of the snake god is pouring out" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4848,TotalExpireTime=120} + +TypeID = 4850 +Name = "the statue of the snake god" +Description = "It is emitting an eerie light" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=3,LightColor=102} + +TypeID = 4851 +Name = "a smashed stone head" +Description = "It seems to repair itself rapidly" +Flags = {Bottom,Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4850,TotalExpireTime=60} + +TypeID = 4852 +Name = "an ectoplasm container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=600} + +TypeID = 4853 +Name = "an ectoplasm container" +Description = "It is filled with ectoplasm" +Flags = {Take} +Attributes = {Weight=600} + +TypeID = 4854 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4855 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4856 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4857 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4858 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4859 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4860 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4861 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4862 +Name = "a red carpet" +Flags = {Unmove} + +TypeID = 4863 +Name = "a butterfly conservation kit" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=700} + +TypeID = 4864 +Name = "a butterfly conservation kit" +Description = "It contains a red butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4865 +Name = "a butterfly conservation kit" +Description = "It contains a purple butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4866 +Name = "a butterfly conservation kit" +Description = "It contains a blue butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 4867 +Name = "a botanist's container" +Description = "It is empty" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=1800} + +TypeID = 4868 +Name = "a botanist's container" +Description = "It holds a sample of the jungle bells plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4869 +Name = "a botanist's container" +Description = "It holds a sample of the giant jungle rose" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4870 +Name = "a botanist's container" +Description = "It holds a sample of the witches cauldron plant" +Flags = {Take} +Attributes = {Weight=2000} + +TypeID = 4871 +Name = "an explorer brooch" +Description = "It is the official badge of the explorer society" +Flags = {Take} +Attributes = {Weight=90} + +TypeID = 4872 +Name = "an ice pick" +Description = "It might come in handy in cold regions" +Flags = {UseEvent,MultiUse,Take} +Attributes = {Weight=7000} + +TypeID = 4873 +Name = "a hydra's nest" +Flags = {Bottom,Chest,Unpass,Unmove,Unlay} + +TypeID = 4874 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4875 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4876 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4877 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4878 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4879 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4880 +Name = "swamp" +Flags = {Clip,Unmove,CollisionEvent} + +TypeID = 4881 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 4882 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4883 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4884 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 4885 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4886 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4887 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4888 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4889 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4890 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4891 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4892 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4893 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4894 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4895 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4896 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4897 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4898 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4899 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4900 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4901 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4902 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4903 +Name = "a ship" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4904 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4905 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4906 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4907 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4908 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4909 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4910 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4911 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4912 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4913 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4914 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 4915 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4916 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4917 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4918 +Name = "a ship rail" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4919 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4920 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4921 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4922 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4923 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4924 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4925 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4926 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4927 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4928 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4929 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4930 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4931 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4932 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4933 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4934 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4935 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4936 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4937 +Name = "a ship railing" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4938 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4939 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4940 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4941 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4942 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4943 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4944 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4945 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4946 +Name = "a mast" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4947 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4948 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4949 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4950 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4951 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4952 +Name = "a mast" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4953 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4954 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4955 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4956 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4957 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4958 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4959 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4960 +Name = "a sail" +Flags = {Bank,Unpass,Unmove,Unlay} +Attributes = {Waypoints=1} + +TypeID = 4961 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4962 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4963 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4964 +Name = "a steering wheel" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4965 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4966 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4967 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4968 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4969 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4970 +Name = "a rudder blade" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4971 +Name = "a ventilation grille" +Flags = {Bank,Unmove} +Attributes = {Waypoints=100} + +TypeID = 4972 +Name = "a bollard" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4973 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4974 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4975 +Name = "a figurehead" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 4976 +Name = "an anchor" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4977 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4978 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4979 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4980 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4981 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4982 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4983 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4984 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4985 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4986 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4987 +Name = "a cleat" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4988 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4989 +Name = "a white flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4990 +Name = "a pirate flag" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4991 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4992 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4993 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 4994 +Name = "some sharp icicles" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 4995 +Name = "a canopic jar" +Description = "You feel an eerie presence" +Flags = {Unpass,Unmove,Unlay,Destroy} +Attributes = {DestroyTarget=4996} + +TypeID = 4996 +Name = "the remains of a canopic jar" +Flags = {Unpass,Unmove,Unlay,Expire} +Attributes = {ExpireTarget=4995,TotalExpireTime=300} + +TypeID = 4997 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 4998 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 4999 +Name = "a hawser" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5000 +Name = "a hawser" +Flags = {Unmove} + +TypeID = 5001 +Name = "a ship railing" +Flags = {Bottom,Unpass,Unmove,Unlay} + +TypeID = 5002 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5003 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookEast} + +TypeID = 5004 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay,HookSouth} + +TypeID = 5005 +Name = "a ship cabin wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5006 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5007 +Name = "a closed door" +Description = "It is locked" +Flags = {UseEvent,Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5008 +Name = "a sandstone wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5009 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5010 +Name = "an oriental wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5011 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5012 +Name = "a lava wall" +Flags = {Bottom,Unpass,Unmove,Unthrow,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5013 +Name = "a dead butterfly" +Flags = {Take,Corpse,Expire} +Attributes = {Weight=20,ExpireTarget=4378,TotalExpireTime=1200} + +TypeID = 5014 +Name = "a mandrake" +Flags = {Take} +Attributes = {Weight=180} + +TypeID = 5015 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5016 +Name = "a skull" +Flags = {Unmove} + +TypeID = 5017 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5018 +Name = "some skulls" +Flags = {Unmove} + +TypeID = 5019 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5020 +Name = "a burning skull" +Flags = {Unmove,Hang} +Attributes = {Brightness=3,LightColor=199} + +TypeID = 5021 +Name = "an orichalcum pearl" +Flags = {Cumulative,Take} +Attributes = {Weight=30} + +TypeID = 5022 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5023 +Name = "a magic forcefield" +Description = "You can see the other side through it" +Flags = {Bottom,Unmove,Avoid} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5024 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=215} + +TypeID = 5025 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=208} + +TypeID = 5026 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=201} + +TypeID = 5027 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=194} + +TypeID = 5028 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=187} + +TypeID = 5029 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=180} + +TypeID = 5030 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=214} + +TypeID = 5031 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=207} + +TypeID = 5032 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=200} + +TypeID = 5033 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=193} + +TypeID = 5034 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=186} + +TypeID = 5035 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=213} + +TypeID = 5036 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=206} + +TypeID = 5037 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=199} + +TypeID = 5038 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=192} + +TypeID = 5039 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=212} + +TypeID = 5040 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=205} + +TypeID = 5041 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=198} + +TypeID = 5042 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=211} + +TypeID = 5043 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=204} + +TypeID = 5044 +Name = "a bamboo lamp" +Flags = {Bottom,ChangeUse,Unpass,Unmove,Unlay} +Attributes = {ChangeTarget=2116,Brightness=7,LightColor=210} + +TypeID = 5045 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5046 +Name = "a monkey statue" +Description = "The words 'See no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5047 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5048 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5049 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5050 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5051 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5052 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5053 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5054 +Name = "a flat roof" +Flags = {Unmove} + +TypeID = 5055 +Name = "a monkey statue" +Description = "The words 'Hear no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5056 +Name = "a monkey statue" +Description = "The words 'Speak no evil' are engraved on it" +Flags = {Unpass,Unlay,Destroy} +Attributes = {DestroyTarget=3142} + +TypeID = 5057 +Name = "a snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5058 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5059 +Name = "a small snake head" +Description = "It is emitting poisonous clouds" +Flags = {Unmove} + +TypeID = 5060 +Name = "a small snake head" +Flags = {Unmove} + +TypeID = 5061 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5062 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5063 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5064 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5065 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5066 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5067 +Name = "a burning wall" +Flags = {Unmove} +Attributes = {Brightness=3,LightColor=206} + +TypeID = 5068 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5069 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5070 +Name = "electric sparks" +Flags = {Unmove} +Attributes = {Brightness=2,LightColor=29} + +TypeID = 5071 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5072 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5073 +Name = "electric iron bars" +Flags = {Unpass,Unmove,Unlay} + +TypeID = 5074 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5075 +Name = "a lava fountain" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {Brightness=2,LightColor=199} + +TypeID = 5076 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5077 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5078 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5079 +Name = "a stony pond" +Flags = {Bottom,Unpass,Unmove,Unlay} +Attributes = {FluidSource=WATER} + +TypeID = 5080 +Name = "a panda teddy" +Flags = {UseEvent,Take} +Attributes = {Weight=600} + +TypeID = 5081 +Name = "a ladder" +Flags = {Bank,CollisionEvent,Unmove,Avoid} +Attributes = {Waypoints=160} + +TypeID = 5082 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5083 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5084 +Name = "a closed door" +Flags = {Bottom,Door,Unpass,Unmove,Unthrow,Unlay} + +TypeID = 5085 +Name = "an open door" +Flags = {Top,Door,Unmove} + +TypeID = 5086 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5087 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5088 +Name = "a furniture package" +Description = "It contains a construction kit for a monkey statue" +Flags = {UseEvent,Avoid,Take,Height} +Attributes = {Weight=2500} + +TypeID = 5089 +Name = "a butterfly conservation kit" +Description = "It contains a rare yellow butterfly" +Flags = {Take} +Attributes = {Weight=800} + +TypeID = 5090 +Name = "a treasure map" +Flags = {Text,Take} +Attributes = {Weight=830} \ No newline at end of file diff --git a/data/lib/compat/compat.lua b/data/lib/compat/compat.lua new file mode 100644 index 0000000..aed2428 --- /dev/null +++ b/data/lib/compat/compat.lua @@ -0,0 +1,999 @@ +TRUE = true +FALSE = false + +result.getDataInt = result.getNumber +result.getDataLong = result.getNumber +result.getDataString = result.getString +result.getDataStream = result.getStream + +LUA_ERROR = false +LUA_NO_ERROR = true + +STACKPOS_GROUND = 0 +STACKPOS_FIRST_ITEM_ABOVE_GROUNDTILE = 1 +STACKPOS_SECOND_ITEM_ABOVE_GROUNDTILE = 2 +STACKPOS_THIRD_ITEM_ABOVE_GROUNDTILE = 3 +STACKPOS_FOURTH_ITEM_ABOVE_GROUNDTILE = 4 +STACKPOS_FIFTH_ITEM_ABOVE_GROUNDTILE = 5 +STACKPOS_TOP_CREATURE = 253 +STACKPOS_TOP_FIELD = 254 +STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE = 255 + +THING_TYPE_PLAYER = 1 + 1 +THING_TYPE_MONSTER = 2 + 1 +THING_TYPE_NPC = 3 + 1 + +COMBAT_POISONDAMAGE = COMBAT_EARTHDAMAGE +TALKTYPE_ORANGE_1 = TALKTYPE_MONSTER_SAY +TALKTYPE_ORANGE_2 = TALKTYPE_MONSTER_YELL + +NORTH = DIRECTION_NORTH +EAST = DIRECTION_EAST +SOUTH = DIRECTION_SOUTH +WEST = DIRECTION_WEST +SOUTHWEST = DIRECTION_SOUTHWEST +SOUTHEAST = DIRECTION_SOUTHEAST +NORTHWEST = DIRECTION_NORTHWEST +NORTHEAST = DIRECTION_NORTHEAST + +do + local function CreatureIndex(self, key) + local methods = getmetatable(self) + if key == "uid" then + return methods.getId(self) + elseif key == "type" then + local creatureType = 0 + if methods.isPlayer(self) then + creatureType = THING_TYPE_PLAYER + elseif methods.isMonster(self) then + creatureType = THING_TYPE_MONSTER + elseif methods.isNpc(self) then + creatureType = THING_TYPE_NPC + end + return creatureType + elseif key == "itemid" then + return 1 + elseif key == "actionid" then + return 0 + end + return methods[key] + end + rawgetmetatable("Player").__index = CreatureIndex + rawgetmetatable("Monster").__index = CreatureIndex + rawgetmetatable("Npc").__index = CreatureIndex +end + +do + local function ItemIndex(self, key) + local methods = getmetatable(self) + if key == "itemid" then + return methods.getId(self) + elseif key == "actionid" then + return methods.getActionId(self) + elseif key == "uid" then + return methods.getUniqueId(self) + elseif key == "type" then + return methods.getSubType(self) + end + return methods[key] + end + rawgetmetatable("Item").__index = ItemIndex + rawgetmetatable("Container").__index = ItemIndex + rawgetmetatable("Teleport").__index = ItemIndex +end + +function pushThing(thing) + local t = {uid = 0, itemid = 0, type = 0, actionid = 0} + if thing ~= nil then + if thing:isItem() then + t.uid = thing:getUniqueId() + t.itemid = thing:getId() + if ItemType(t.itemid):hasSubType() then + t.type = thing:getSubType() + end + t.actionid = thing:getActionId() + elseif thing:isCreature() then + t.uid = thing:getId() + t.itemid = 1 + if thing:isPlayer() then + t.type = THING_TYPE_PLAYER + elseif thing:isMonster() then + t.type = THING_TYPE_MONSTER + else + t.type = THING_TYPE_NPC + end + end + end + return t +end + +createCombatObject = Combat +setCombatArea = Combat.setArea +setCombatCallback = Combat.setCallback +setCombatCondition = Combat.setCondition +setCombatFormula = Combat.setFormula +setCombatParam = Combat.setParameter + +createConditionObject = Condition +setConditionParam = Condition.setParameter +setConditionFormula = Condition.setFormula +addDamageCondition = Condition.addDamage +addOutfitCondition = Condition.setOutfit + +function doCombat(cid, combat, var) return combat:execute(cid, var) end + +function isCreature(cid) return Creature(cid) ~= nil end +function isPlayer(cid) return Player(cid) ~= nil end +function isMonster(cid) return Monster(cid) ~= nil end +function isSummon(cid) return Creature(cid):getMaster() ~= nil end +function isNpc(cid) return Npc(cid) ~= nil end +function isItem(uid) return Item(uid) ~= nil end +function isContainer(uid) return Container(uid) ~= nil end + +function getCreatureName(cid) local c = Creature(cid) return c ~= nil and c:getName() or false end +function getCreatureHealth(cid) local c = Creature(cid) return c ~= nil and c:getHealth() or false end +function getCreatureMaxHealth(cid) local c = Creature(cid) return c ~= nil and c:getMaxHealth() or false end +function getCreaturePosition(cid) local c = Creature(cid) return c ~= nil and c:getPosition() or false end +function getCreatureOutfit(cid) local c = Creature(cid) return c ~= nil and c:getOutfit() or false end +function getCreatureSpeed(cid) local c = Creature(cid) return c ~= nil and c:getSpeed() or false end +function getCreatureBaseSpeed(cid) local c = Creature(cid) return c ~= nil and c:getBaseSpeed() or false end + +function getCreatureTarget(cid) + local c = Creature(cid) + if c ~= nil then + local target = c:getTarget() + return target ~= nil and target:getId() or 0 + end + return false +end + +function getCreatureMaster(cid) + local c = Creature(cid) + if c ~= nil then + local master = c:getMaster() + return master ~= nil and master:getId() or c:getId() + end + return false +end + +function getCreatureSummons(cid) + local c = Creature(cid) + if c == nil then + return false + end + + local result = {} + for _, summon in ipairs(c:getSummons()) do + result[#result + 1] = summon:getId() + end + return result +end + +getCreaturePos = getCreaturePosition + +function doCreatureAddHealth(cid, health) local c = Creature(cid) return c ~= nil and c:addHealth(health) or false end +function doRemoveCreature(cid) local c = Creature(cid) return c ~= nil and c:remove() or false end +function doCreatureSetLookDir(cid, direction) local c = Creature(cid) return c ~= nil and c:setDirection(direction) or false end +function doCreatureSay(cid, text, type, ...) local c = Creature(cid) return c ~= nil and c:say(text, type, ...) or false end +function doCreatureChangeOutfit(cid, outfit) local c = Creature(cid) return c ~= nil and c:setOutfit(outfit) or false end +function doSetCreatureDropLoot(cid, doDrop) local c = Creature(cid) return c ~= nil and c:setDropLoot(doDrop) or false end +function doChangeSpeed(cid, delta) local c = Creature(cid) return c ~= nil and c:changeSpeed(delta) or false end +function doAddCondition(cid, conditionId) local c = Creature(cid) return c ~= nil and c:addCondition(conditionId) or false end +function doRemoveCondition(cid, conditionType, subId) local c = Creature(cid) return c ~= nil and (c:removeCondition(conditionType, CONDITIONID_COMBAT, subId) or c:removeCondition(conditionType, CONDITIONID_DEFAULT, subId) or true) end + +doSetCreatureDirection = doCreatureSetLookDir + +function registerCreatureEvent(cid, name) local c = Creature(cid) return c ~= nil and c:registerEvent(name) or false end +function unregisterCreatureEvent(cid, name) local c = Creature(cid) return c ~= nil and c:unregisterEvent(name) or false end + +function getPlayerByName(name) local p = Player(name) return p ~= nil and p:getId() or false end +function getIPByPlayerName(name) local p = Player(name) return p ~= nil and p:getIp() or false end +function getPlayerGUID(cid) local p = Player(cid) return p ~= nil and p:getGuid() or false end +function getPlayerIp(cid) local p = Player(cid) return p ~= nil and p:getIp() or false end +function getPlayerAccountType(cid) local p = Player(cid) return p ~= nil and p:getAccountType() or false end +function getPlayerLastLoginSaved(cid) local p = Player(cid) return p ~= nil and p:getLastLoginSaved() or false end +function getPlayerName(cid) local p = Player(cid) return p ~= nil and p:getName() or false end +function getPlayerFreeCap(cid) local p = Player(cid) return p ~= nil and (p:getFreeCapacity() / 100) or false end +function getPlayerPosition(cid) local p = Player(cid) return p ~= nil and p:getPosition() or false end +function getPlayerMagLevel(cid) local p = Player(cid) return p ~= nil and p:getMagicLevel() or false end +function getPlayerAccess(cid) + local player = Player(cid) + if player == nil then + return false + end + return player:getGroup():getAccess() and 1 or 0 +end +function getPlayerSkill(cid, skillId) local p = Player(cid) return p ~= nil and p:getSkillLevel(skillId) or false end +function getPlayerMana(cid) local p = Player(cid) return p ~= nil and p:getMana() or false end +function getPlayerMaxMana(cid) local p = Player(cid) return p ~= nil and p:getMaxMana() or false end +function getPlayerLevel(cid) local p = Player(cid) return p ~= nil and p:getLevel() or false end +function getPlayerTown(cid) local p = Player(cid) return p ~= nil and p:getTown():getId() or false end +function getPlayerVocation(cid) local p = Player(cid) return p ~= nil and p:getVocation():getId() or false end +function getPlayerSoul(cid) local p = Player(cid) return p ~= nil and p:getSoul() or false end +function getPlayerSex(cid) local p = Player(cid) return p ~= nil and p:getSex() or false end +function getPlayerStorageValue(cid, key) local p = Player(cid) return p ~= nil and p:getStorageValue(key) or false end +function getPlayerBalance(cid) local p = Player(cid) return p ~= nil and p:getBankBalance() or false end +function getPlayerMoney(cid) local p = Player(cid) return p ~= nil and p:getMoney() or false end +function getPlayerGroupId(cid) local p = Player(cid) return p ~= nil and p:getGroup():getId() or false end +function getPlayerLookDir(cid) local p = Player(cid) return p ~= nil and p:getDirection() or false end +function getPlayerLight(cid) local p = Player(cid) return p ~= nil and p:getLight() or false end +function getPlayerDepotItems(cid, depotId) local p = Player(cid) return p ~= nil and p:getDepotItems(depotId) or false end +function getPlayerSkullType(cid) local p = Player(cid) return p ~= nil and p:getSkull() or false end +function getPlayerLossPercent(cid) local p = Player(cid) return p ~= nil and p:getDeathPenalty() or false end +function getPlayerPremiumDays(cid) local p = Player(cid) return p ~= nil and p:getPremiumDays() or false end +function getPlayerBlessing(cid, blessing) local p = Player(cid) return p ~= nil and p:hasBlessing(blessing) or false end +function getPlayerParty(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return nil + end + return party:getLeader():getId() +end +function getPlayerGuildId(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getId() +end +function getPlayerGuildLevel(cid) local p = Player(cid) return p ~= nil and p:getGuildLevel() or false end +function getPlayerGuildName(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + return guild:getName() +end +function getPlayerGuildRank(cid) + local player = Player(cid) + if player == nil then + return false + end + + local guild = player:getGuild() + if guild == nil then + return false + end + + local rank = guild:getRankByLevel(player:getGuildLevel()) + return rank ~= nil and rank.name or false +end +function getPlayerGuildNick(cid) local p = Player(cid) return p ~= nil and p:getGuildNick() or false end +function getPlayerMasterPos(cid) local p = Player(cid) return p ~= nil and p:getTown():getTemplePosition() or false end +function getPlayerItemCount(cid, itemId, ...) local p = Player(cid) return p ~= nil and p:getItemCount(itemId, ...) or false end +function getPlayerSlotItem(cid, slot) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getSlotItem(slot)) +end +function getPlayerItemById(cid, deepSearch, itemId, ...) + local player = Player(cid) + if player == nil then + return pushThing(nil) + end + return pushThing(player:getItemById(itemId, deepSearch, ...)) +end +function getPlayerFood(cid) + local player = Player(cid) + if player == nil then + return false + end + local c = player:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) return c ~= nil and math.floor(c:getTicks() / 1000) or 0 +end +function canPlayerLearnInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:canLearnSpell(name) or false end +function getPlayerLearnedInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:hasLearnedSpell(name) or false end +function isPlayerGhost(cid) local p = Player(cid) return p ~= nil and p:isInGhostMode() or false end +function isPlayerPzLocked(cid) local p = Player(cid) return p ~= nil and p:isPzLocked() or false end +function isPremium(cid) local p = Player(cid) return p ~= nil and p:isPremium() or false end +function getPlayersByIPAddress(ip, mask) + if mask == nil then mask = 0xFFFFFFFF end + local masked = bit.band(ip, mask) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if bit.band(player:getIp(), mask) == masked then + result[#result + 1] = player:getId() + end + end + return result +end +function getOnlinePlayers() + local result = {} + for _, player in ipairs(Game.getPlayers()) do + result[#result + 1] = player:getName() + end + return result +end +function getPlayersByAccountNumber(accountNumber) + local result = {} + for _, player in ipairs(Game.getPlayers()) do + if player:getAccountId() == accountNumber then + result[#result + 1] = player:getId() + end + end + return result +end +function getPlayerGUIDByName(name) + local player = Player(name) + if player ~= nil then + return player:getGuid() + end + + local resultId = db.storeQuery("SELECT `id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local guid = result.getDataInt(resultId, "id") + result.free(resultId) + return guid + end + return 0 +end +function getAccountNumberByPlayerName(name) + local player = Player(name) + if player ~= nil then + return player:getAccountId() + end + + local resultId = db.storeQuery("SELECT `account_id` FROM `players` WHERE `name` = " .. db.escapeString(name)) + if resultId ~= false then + local accountId = result.getDataInt(resultId, "account_id") + result.free(resultId) + return accountId + end + return 0 +end + +getPlayerAccountBalance = getPlayerBalance +getIpByName = getIPByPlayerName + +function setPlayerStorageValue(cid, key, value) local p = Player(cid) return p ~= nil and p:setStorageValue(key, value) or false end +function doPlayerSetBalance(cid, balance) local p = Player(cid) return p ~= nil and p:setBankBalance(balance) or false end +function doPlayerAddMoney(cid, money) local p = Player(cid) return p ~= nil and p:addMoney(money) or false end +function doPlayerRemoveMoney(cid, money) local p = Player(cid) return p ~= nil and p:removeMoney(money) or false end +function doPlayerAddSoul(cid, soul) local p = Player(cid) return p ~= nil and p:addSoul(soul) or false end +function doPlayerSetVocation(cid, vocation) local p = Player(cid) return p ~= nil and p:setVocation(Vocation(vocation)) or false end +function doPlayerSetTown(cid, town) local p = Player(cid) return p ~= nil and p:setTown(Town(town)) or false end +function setPlayerGroupId(cid, groupId) local p = Player(cid) return p ~= nil and p:setGroup(Group(groupId)) or false end +function doPlayerSetSex(cid, sex) local p = Player(cid) return p ~= nil and p:setSex(sex) or false end +function doPlayerSetGuildLevel(cid, level) local p = Player(cid) return p ~= nil and p:setGuildLevel(level) or false end +function doPlayerSetGuildNick(cid, nick) local p = Player(cid) return p ~= nil and p:setGuildNick(nick) or false end +function doShowTextDialog(cid, itemId, text) local p = Player(cid) return p ~= nil and p:showTextDialog(itemId, text) or false end +function doPlayerAddItemEx(cid, uid, ...) local p = Player(cid) return p ~= nil and p:addItemEx(Item(uid), ...) or false end +function doPlayerRemoveItem(cid, itemid, count, ...) local p = Player(cid) return p ~= nil and p:removeItem(itemid, count, ...) or false end +function doPlayerAddPremiumDays(cid, days) local p = Player(cid) return p ~= nil and p:addPremiumDays(days) or false end +function doPlayerRemovePremiumDays(cid, days) local p = Player(cid) return p ~= nil and p:removePremiumDays(days) or false end +function doPlayerAddBlessing(cid, blessing) local p = Player(cid) return p ~= nil and p:addBlessing(blessing) or false end +function doPlayerSendCancel(cid, text) local p = Player(cid) return p ~= nil and p:sendCancelMessage(text) or false end +function doPlayerFeed(cid, food) local p = Player(cid) return p ~= nil and p:feed(food) or false end +function playerLearnInstantSpell(cid, name) local p = Player(cid) return p ~= nil and p:learnSpell(name) or false end +function doPlayerPopupFYI(cid, message) local p = Player(cid) return p ~= nil and p:popupFYI(message) or false end +function doPlayerSendTextMessage(cid, type, text, ...) local p = Player(cid) return p ~= nil and p:sendTextMessage(type, text, ...) or false end +function doSendAnimatedText() debugPrint("Deprecated function.") return true end +function doPlayerAddExp(cid, exp, useMult, ...) + local player = Player(cid) + if player == nil then + return false + end + + if useMult then + exp = exp * Game.getExperienceStage(player:getLevel()) + end + return player:addExperience(exp, ...) +end +function doPlayerAddManaSpent(cid, mana) local p = Player(cid) return p ~= nil and p:addManaSpent(mana * configManager.getNumber(configKeys.RATE_MAGIC)) or false end +function doPlayerAddSkillTry(cid, skillid, n) local p = Player(cid) return p ~= nil and p:addSkillTries(skillid, n * configManager.getNumber(configKeys.RATE_SKILL)) or false end +function doPlayerAddMana(cid, mana, ...) local p = Player(cid) return p ~= nil and p:addMana(mana, ...) or false end +function doPlayerJoinParty(cid, leaderId) + local player = Player(cid) + if player == nil then + return false + end + + if player:getParty() ~= nil then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party.") + return true + end + + local leader = Player(leaderId) + if leader == nil then + return false + end + + local party = leader:getParty() + if party == nil or party:getLeader() ~= leader then + return true + end + + for _, invitee in ipairs(party:getInvitees()) do + if player ~= invitee then + return true + end + end + + party:addMember(player) + return true +end +function getPartyMembers(cid) + local player = Player(cid) + if player == nil then + return false + end + + local party = player:getParty() + if party == nil then + return false + end + + local result = {party:getLeader():getId()} + for _, member in ipairs(party:getMembers()) do + result[#result + 1] = member:getId() + end + return result +end + +doPlayerSendDefaultCancel = doPlayerSendCancel + +function getMonsterTargetList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local result = {} + for _, creature in ipairs(monster:getTargetList()) do + if monster:isTarget(creature) then + result[#result + 1] = creature:getId() + end + end + return result +end +function getMonsterFriendList(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + local z = monster:getPosition().z + + local result = {} + for _, creature in ipairs(monster:getFriendList()) do + if not creature:isRemoved() and creature:getPosition().z == z then + result[#result + 1] = creature:getId() + end + end + return result +end +function doSetMonsterTarget(cid, target) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() ~= nil then + return true + end + + local target = Creature(cid) + if target == nil then + return false + end + + monster:selectTarget(target) + return true +end +function doMonsterChangeTarget(cid) + local monster = Monster(cid) + if monster == nil then + return false + end + + if monster:getMaster() ~= nil then + return true + end + + monster:searchTarget(1) + return true +end +function doCreateNpc(name, pos, ...) + local npc = Game.createNpc(name, pos, ...) return npc ~= nil and npc:setMasterPos(pos) or false +end +function doSummonCreature(name, pos, ...) + local m = Game.createMonster(name, pos, ...) return m ~= nil and m:getId() or false +end +function doConvinceCreature(cid, target) + local creature = Creature(cid) + if creature == nil then + return false + end + + local targetCreature = Creature(target) + if targetCreature == nil then + return false + end + + targetCreature:setMaster(creature) + return true +end + +function getTownId(townName) local t = Town(townName) return t ~= nil and t:getId() or false end +function getTownName(townId) local t = Town(townId) return t ~= nil and t:getName() or false end +function getTownTemplePosition(townId) local t = Town(townId) return t ~= nil and t:getTemplePosition() or false end + +function doSetItemActionId(uid, actionId) local i = Item(uid) return i ~= nil and i:setActionId(actionId) or false end +function doTransformItem(uid, newItemId, ...) local i = Item(uid) return i ~= nil and i:transform(newItemId, ...) or false end +function doChangeTypeItem(uid, newType) local i = Item(uid) return i ~= nil and i:transform(i:getId(), newType) or false end +function doRemoveItem(uid, ...) local i = Item(uid) return i ~= nil and i:remove(...) or false end + +function getContainerSize(uid) local c = Container(uid) return c ~= nil and c:getSize() or false end +function getContainerCap(uid) local c = Container(uid) return c ~= nil and c:getCapacity() or false end +function getContainerItem(uid, slot) + local container = Container(uid) + if container == nil then + return pushThing(nil) + end + return pushThing(container:getItem(slot)) +end + +function doAddContainerItemEx(uid, virtualId) + local container = Container(uid) + if container == nil then + return false + end + + local res = container:addItemEx(Item(virtualId)) + if res == nil then + return false + end + return res +end + +function doSendMagicEffect(pos, magicEffect, ...) return Position(pos):sendMagicEffect(magicEffect, ...) end +function doSendDistanceShoot(fromPos, toPos, distanceEffect, ...) return Position(fromPos):sendDistanceEffect(toPos, distanceEffect, ...) end +function isSightClear(fromPos, toPos, floorCheck) return Position(fromPos):isSightClear(toPos, floorCheck) end + +function getPromotedVocation(vocationId) + local vocation = Vocation(vocationId) + if vocation == nil then + return 0 + end + + local promotedVocation = vocation:getPromotion() + if promotedVocation == nil then + return 0 + end + return promotedVocation:getId() +end + +function getGuildId(guildName) + local resultId = db.storeQuery("SELECT `id` FROM `guilds` WHERE `name` = " .. db.escapeString(guildName)) + if resultId == false then + return false + end + + local guildId = result.getDataInt(resultId, "id") + result.free(resultId) + return guildId +end + +function getHouseName(houseId) local h = House(houseId) return h ~= nil and h:getName() or false end +function getHouseOwner(houseId) local h = House(houseId) return h ~= nil and h:getOwnerGuid() or false end +function getHouseEntry(houseId) local h = House(houseId) return h ~= nil and h:getExitPosition() or false end +function getHouseTown(houseId) local h = House(houseId) if h == nil then return false end local t = h:getTown() return t ~= nil and t:getId() or false end +function getHouseTilesSize(houseId) local h = House(houseId) return h ~= nil and h:getTileCount() or false end + +function isItemStackable(itemId) return ItemType(itemId):isStackable() end +function isItemRune(itemId) return ItemType(itemId):isRune() end +function isItemDoor(itemId) return ItemType(itemId):isDoor() end +function isItemContainer(itemId) return ItemType(itemId):isContainer() end +function isItemFluidContainer(itemId) return ItemType(itemId):isFluidContainer() end +function isItemMovable(itemId) return ItemType(itemId):isMovable() end +function isCorpse(uid) local i = Item(uid) return i ~= nil and ItemType(i:getId()):isCorpse() or false end + +isItemMoveable = isItemMovable +isMoveable = isMovable + +function getItemName(itemId) return ItemType(itemId):getName() end +function getItemWeight(itemId, ...) return ItemType(itemId):getWeight(...) / 100 end +function getItemDescriptions(itemId) + local itemType = ItemType(itemId) + return { + name = itemType:getName(), + plural = itemType:getPluralName(), + article = itemType:getArticle(), + description = itemType:getDescription() + } +end +function getItemIdByName(name) + local id = ItemType(name):getId() + if id == 0 then + return false + end + return id +end +function getItemWeightByUID(uid, ...) + local item = Item(uid) + if item == nil then + return false + end + + local itemType = ItemType(item:getId()) + return itemType:isStackable() and (itemType:getWeight(item:getCount(), ...) / 100) or (itemType:getWeight(1, ...) / 100) +end +function getItemRWInfo(uid) + local item = Item(uid) + if item == nil then + return false + end + + local rwFlags = 0 + local itemType = ItemType(item:getId()) + if itemType:isReadable() then + rwFlags = bit.bor(rwFlags, 1) + end + + if itemType:isWritable() then + rwFlags = bit.bor(rwFlags, 2) + end + return rwFlags +end +function getContainerCapById(itemId) return ItemType(itemId):getCapacity() end +function getFluidSourceType(itemId) local it = ItemType(itemId) return it.id ~= 0 and it:getFluidSource() or false end +function hasProperty(uid, prop) + local item = Item(uid) + if item == nil then + return false + end + + local parent = item:getParent() + if parent:isTile() and item == parent:getGround() then + return parent:hasProperty(prop) + else + return item:hasProperty(prop) + end +end + +function doSetItemText(uid, text) + local item = Item(uid) + if item == nil then + return false + end + + if text ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_TEXT, text) + else + item:removeAttribute(ITEM_ATTRIBUTE_TEXT) + end + return true +end +function doSetItemSpecialDescription(uid, desc) + local item = Item(uid) + if item == nil then + return false + end + + if desc ~= "" then + item:setAttribute(ITEM_ATTRIBUTE_DESCRIPTION, desc) + else + item:removeAttribute(ITEM_ATTRIBUTE_DESCRIPTION) + end + return true +end +function doDecayItem(uid) local i = Item(uid) return i ~= nil and i:decay() or false end + +function setHouseOwner(id, guid) local h = House(id) return h ~= nil and h:setOwnerGuid(guid) or false end +function getHouseRent(id) local h = House(id) return h ~= nil and h:getRent() or nil end +function getHouseAccessList(id, listId) local h = House(id) return h ~= nil and h:getAccessList(listId) or nil end +function setHouseAccessList(id, listId, listText) local h = House(id) return h ~= nil and h:setAccessList(listId, listText) or false end + +function getHouseByPlayerGUID(playerGUID) + for _, house in ipairs(Game.getHouses()) do + if house:getOwnerGuid() == playerGUID then + return house:getId() + end + end + return nil +end + +function getTileHouseInfo(pos) + local t = Tile(pos) + if t == nil then + return false + end + local h = t:getHouse() + return h ~= nil and h:getId() or false +end + +function getTilePzInfo(position) + local t = Tile(position) + if t == nil then + return false + end + return t:hasFlag(TILESTATE_PROTECTIONZONE) +end + +function getTileInfo(position) + local t = Tile(position) + if t == nil then + return false + end + + local ret = pushThing(t:getGround()) + ret.protection = t:hasFlag(TILESTATE_PROTECTIONZONE) + ret.nopz = ret.protection + ret.nologout = t:hasFlag(TILESTATE_NOLOGOUT) + ret.refresh = t:hasFlag(TILESTATE_REFRESH) + ret.house = t:getHouse() ~= nil + ret.bed = t:hasFlag(TILESTATE_BED) + ret.depot = t:hasFlag(TILESTATE_DEPOT) + + ret.things = t:getThingCount() + ret.creatures = t:getCreatureCount() + ret.items = t:getItemCount() + ret.topItems = t:getTopItemCount() + ret.downItems = t:getDownItemCount() + return ret +end + +function getTileItemByType(position, itemType) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByType(itemType)) +end + +function getTileItemById(position, itemId, ...) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemById(itemId, ...)) +end + +function getTileThingByPos(position) + local t = Tile(position) + if t == nil then + if position.stackpos == -1 then + return -1 + end + return pushThing(nil) + end + + if position.stackpos == -1 then + return t:getThingCount() + end + return pushThing(t:getThing(position.stackpos)) +end + +function getTileThingByTopOrder(position, topOrder) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getItemByTopOrder(topOrder)) +end + +function getTopCreature(position) + local t = Tile(position) + if t == nil then + return pushThing(nil) + end + return pushThing(t:getTopCreature()) +end + +function queryTileAddThing(thing, position, ...) local t = Tile(position) return t ~= nil and t:queryAdd(thing, ...) or false end + +function doTeleportThing(uid, dest, pushMovement) + if type(uid) == "userdata" then + if uid:isCreature() then + return uid:teleportTo(dest, pushMovement or false) + else + return uid:moveTo(dest) + end + else + if uid >= 0x10000000 then + local creature = Creature(uid) + if creature ~= nil then + return creature:teleportTo(dest, pushMovement or false) + end + else + local item = Item(uid) + if item ~= nil then + return item:moveTo(dest) + end + end + end + return false +end + +function getThingPos(uid) + local thing + if type(uid) ~= "userdata" then + if uid >= 0x10000000 then + thing = Creature(uid) + else + thing = Item(uid) + end + else + thing = uid + end + + if thing == nil then + return false + end + + local stackpos = 0 + local tile = thing:getTile() + if tile ~= nil then + stackpos = tile:getThingIndex(thing) + end + + local position = thing:getPosition() + position.stackpos = stackpos + return position +end + +function getThingfromPos(pos) + local tile = Tile(pos) + if tile == nil then + return pushThing(nil) + end + + local thing + local stackpos = pos.stackpos or 0 + if stackpos == STACKPOS_TOP_MOVEABLE_ITEM_OR_CREATURE then + thing = tile:getTopCreature() + if thing == nil then + local item = tile:getTopDownItem() + if item ~= nil and item:getType():isMovable() then + thing = item + end + end + elseif stackpos == STACKPOS_TOP_FIELD then + thing = tile:getFieldItem() + elseif stackpos == STACKPOS_TOP_CREATURE then + thing = tile:getTopCreature() + else + thing = tile:getThing(stackpos) + end + return pushThing(thing) +end + +function doRelocate(fromPos, toPos, unmovables) + if fromPos == toPos then + return false + end + + local fromTile = Tile(fromPos) + if fromTile == nil then + return false + end + + if Tile(toPos) == nil then + return false + end + + local ignoreUnmovables = false + if unmovables ~= nil then + ignoreUnmovables = unmovables + end + + for i = fromTile:getThingCount() - 1, 0, -1 do + local thing = fromTile:getThing(i) + if thing ~= nil then + if thing:isItem() and not ItemType(thing:getId()):isGroundTile() then + if ignoreUnmovables and not ItemType(thing:getId()):isMovable() then + thing:moveTo(toPos) + elseif ItemType(thing:getId()):isMovable() then + thing:moveTo(toPos) + elseif ItemType(thing:getId()):isMagicField() then + thing:remove() + elseif ItemType(thing:getId()):isSplash() then + thing:remove() + end + elseif thing:isCreature() then + thing:teleportTo(toPos) + end + end + end + return true +end + +function getThing(uid) + return uid >= 0x10000000 and pushThing(Creature(uid)) or pushThing(Item(uid)) +end + +function getConfigInfo(info) + if type(info) ~= "string" then + return nil + end + dofile('config.lua') + return _G[info] +end + +function getWorldCreatures(type) + if type == 0 then + return Game.getPlayerCount() + elseif type == 1 then + return Game.getMonsterCount() + elseif type == 2 then + return Game.getNpcCount() + end + return Game.getPlayerCount() + Game.getMonsterCount() + Game.getNpcCount() +end + +saveData = saveServer + +function getGlobalStorageValue(key) + return Game.getStorageValue(key) or -1 +end + +function setGlobalStorageValue(key, value) + Game.setStorageValue(key, value) + return true +end + +getWorldType = Game.getWorldType + +numberToVariant = Variant +stringToVariant = Variant +positionToVariant = Variant + +function targetPositionToVariant(position) + local variant = Variant(position) + variant.type = VARIANT_TARGETPOSITION + return variant +end + +variantToNumber = Variant.getNumber +variantToString = Variant.getString +variantToPosition = Variant.getPosition + +function doCreateTeleport(itemId, destination, position) + local item = Game.createItem(itemId, 1, position) + if not item:isTeleport() then + item:remove() + return false + end + item:setDestination(destination) + return item:getUniqueId() +end + +function getSpectators(centerPos, rangex, rangey, multifloor, onlyPlayers) + local result = Game.getSpectators(centerPos, multifloor, onlyPlayers or false, rangex, rangex, rangey, rangey) + if #result == 0 then + return nil + end + + for index, spectator in ipairs(result) do + result[index] = spectator:getId() + end + return result +end + +function broadcastMessage(message, messageType) + Game.broadcastMessage(message, messageType) + print("> Broadcasted message: \"" .. message .. "\".") +end + +function Guild.addMember(self, player) + return player:setGuild(guild) +end +function Guild.removeMember(self, player) + return player:getGuild() == self and player:setGuild(nil) +end diff --git a/data/lib/core/constants.lua b/data/lib/core/constants.lua new file mode 100644 index 0000000..9d1f90b --- /dev/null +++ b/data/lib/core/constants.lua @@ -0,0 +1 @@ +CONTAINER_POSITION = 0xFFFF diff --git a/data/lib/core/container.lua b/data/lib/core/container.lua new file mode 100644 index 0000000..1898732 --- /dev/null +++ b/data/lib/core/container.lua @@ -0,0 +1,27 @@ +function Container.isContainer(self) + return true +end + +function Container.isItem(self) + return true +end + +function Container.isMonster(self) + return false +end + +function Container.isCreature(self) + return false +end + +function Container.isPlayer(self) + return false +end + +function Container.isTeleport(self) + return false +end + +function Container.isTile(self) + return false +end diff --git a/data/lib/core/core.lua b/data/lib/core/core.lua new file mode 100644 index 0000000..9491e80 --- /dev/null +++ b/data/lib/core/core.lua @@ -0,0 +1,11 @@ +dofile('data/lib/core/constants.lua') +dofile('data/lib/core/container.lua') +dofile('data/lib/core/creature.lua') +dofile('data/lib/core/monster.lua') +dofile('data/lib/core/game.lua') +dofile('data/lib/core/item.lua') +dofile('data/lib/core/itemtype.lua') +dofile('data/lib/core/player.lua') +dofile('data/lib/core/position.lua') +dofile('data/lib/core/teleport.lua') +dofile('data/lib/core/tile.lua') diff --git a/data/lib/core/creature.lua b/data/lib/core/creature.lua new file mode 100644 index 0000000..3393fdb --- /dev/null +++ b/data/lib/core/creature.lua @@ -0,0 +1,116 @@ +function Creature.getClosestFreePosition(self, position, extended) + local usePosition = Position(position) + local tiles = { Tile(usePosition) } + local length = extended and 2 or 1 + + local tile + for y = -length, length do + for x = -length, length do + if x ~= 0 or y ~= 0 then + usePosition.x = position.x + x + usePosition.y = position.y + y + + tile = Tile(usePosition) + if tile then + tiles[#tiles + 1] = tile + end + end + end + end + + for i = 1, #tiles do + tile = tiles[i] + if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) then + return tile:getPosition() + end + end + return Position() +end + +function Creature:setMonsterOutfit(monster, time) + local monsterType = MonsterType(monster) + if not monsterType then + return false + end + + if self:isPlayer() and not (getPlayerFlagValue(self, PlayerFlag_CanIllusionAll) or monsterType:isIllusionable()) then + return false + end + + local condition = Condition(CONDITION_OUTFIT) + condition:setOutfit(monsterType:getOutfit()) + condition:setTicks(time) + self:addCondition(condition) + + return true +end + +function Creature:setItemOutfit(item, time) + local itemType = ItemType(item) + if not itemType then + return false + end + + local condition = Condition(CONDITION_OUTFIT) + condition:setOutfit({ + lookTypeEx = itemType:getId() + }) + condition:setTicks(time) + self:addCondition(condition) + + return true +end + +function Creature:addSummon(monster) + local summon = Monster(monster) + if not summon then + return false + end + + summon:setTarget(nil) + summon:setFollowCreature(nil) + summon:setDropLoot(false) + summon:setMaster(self) + return true +end + +function Creature:removeSummon(monster) + local summon = Monster(monster) + if not summon or summon:getMaster() ~= self then + return false + end + + summon:setTarget(nil) + summon:setFollowCreature(nil) + summon:setDropLoot(true) + summon:setMaster(nil) + return true +end + +function Creature.getPlayer(self) + return self:isPlayer() and self or nil +end + +function Creature.isItem(self) + return false +end + +function Creature.isMonster(self) + return false +end + +function Creature.isNpc(self) + return false +end + +function Creature.isPlayer(self) + return false +end + +function Creature.isTile(self) + return false +end + +function Creature.isContainer(self) + return false +end \ No newline at end of file diff --git a/data/lib/core/game.lua b/data/lib/core/game.lua new file mode 100644 index 0000000..29cacef --- /dev/null +++ b/data/lib/core/game.lua @@ -0,0 +1,125 @@ +function Game.sendMagicEffect(position, effect) + local pos = Position(position) + pos:sendMagicEffect(effect) +end + +function Game.removeItemsOnMap(position) + local tile = Tile(position) + local tileCount = tile:getThingCount() + local i = 0 + while i < tileCount do + local tileItem = tile:getThing(i) + if tileItem and tileItem:getType():isMovable() then + tileItem:remove() + else + i = i + 1 + end + end +end + +function Game.transformItemOnMap(position, itemId, toItemId, subtype) + if not subtype then + subtype = -1 + end + + local tile = Tile(position) + local item = tile:getItemById(itemId) + item:transform(toItemId, subtype) + item:decay() + return item +end + +function Game.removeItemOnMap(position, itemId, subtype) + if not subtype then + subtype = -1 + end + + local tile = Tile(position) + local item = tile:getItemById(itemId, subtype) + item:remove() +end + +function Game.isItemThere(position, itemId) + local tile = Tile(position) + return tile:getItemById(itemId) ~= nil +end + +function Game.isPlayerThere(position) + local tile = Tile(position) + local creatures = tile:getCreatures() + for _, creature in ipairs(creatures) do + if creature:isPlayer() then + return true + end + end + return false +end + +function Game.broadcastMessage(message, messageType) + if messageType == nil then + messageType = MESSAGE_STATUS_WARNING + end + + for _, player in ipairs(Game.getPlayers()) do + player:sendTextMessage(messageType, message) + end +end + +function Game.convertIpToString(ip) + local band = bit.band + local rshift = bit.rshift + return string.format("%d.%d.%d.%d", + band(ip, 0xFF), + band(rshift(ip, 8), 0xFF), + band(rshift(ip, 16), 0xFF), + rshift(ip, 24) + ) +end + +function Game.getReverseDirection(direction) + if direction == WEST then + return EAST + elseif direction == EAST then + return WEST + elseif direction == NORTH then + return SOUTH + elseif direction == SOUTH then + return NORTH + elseif direction == NORTHWEST then + return SOUTHEAST + elseif direction == NORTHEAST then + return SOUTHWEST + elseif direction == SOUTHWEST then + return NORTHEAST + elseif direction == SOUTHEAST then + return NORTHWEST + end + return NORTH +end + +function Game.getSkillType(weaponType) + if weaponType == WEAPON_CLUB then + return SKILL_CLUB + elseif weaponType == WEAPON_SWORD then + return SKILL_SWORD + elseif weaponType == WEAPON_AXE then + return SKILL_AXE + elseif weaponType == WEAPON_DISTANCE then + return SKILL_DISTANCE + elseif weaponType == WEAPON_SHIELD then + return SKILL_SHIELD + end + return SKILL_FIST +end + +if not globalStorageTable then + globalStorageTable = {} +end + +function Game.getStorageValue(key) + return globalStorageTable[key] +end + +function Game.setStorageValue(key, value) + globalStorageTable[key] = value +end diff --git a/data/lib/core/item.lua b/data/lib/core/item.lua new file mode 100644 index 0000000..8aa0972 --- /dev/null +++ b/data/lib/core/item.lua @@ -0,0 +1,31 @@ +function Item.getType(self) + return ItemType(self:getId()) +end + +function Item.isItem(self) + return true +end + +function Item.isContainer(self) + return false +end + +function Item.isCreature(self) + return false +end + +function Item.isMonster(self) + return false +end + +function Item.isPlayer(self) + return false +end + +function Item.isTeleport(self) + return false +end + +function Item.isTile(self) + return false +end diff --git a/data/lib/core/itemtype.lua b/data/lib/core/itemtype.lua new file mode 100644 index 0000000..c94ba7f --- /dev/null +++ b/data/lib/core/itemtype.lua @@ -0,0 +1,16 @@ +local slotBits = { + [CONST_SLOT_HEAD] = SLOTP_HEAD, + [CONST_SLOT_NECKLACE] = SLOTP_NECKLACE, + [CONST_SLOT_BACKPACK] = SLOTP_BACKPACK, + [CONST_SLOT_ARMOR] = SLOTP_ARMOR, + [CONST_SLOT_RIGHT] = SLOTP_RIGHT, + [CONST_SLOT_LEFT] = SLOTP_LEFT, + [CONST_SLOT_LEGS] = SLOTP_LEGS, + [CONST_SLOT_FEET] = SLOTP_FEET, + [CONST_SLOT_RING] = SLOTP_RING, + [CONST_SLOT_AMMO] = SLOTP_AMMO +} + +function ItemType.usesSlot(self, slot) + return bit.band(self:getSlotPosition(), slotBits[slot] or 0) ~= 0 +end diff --git a/data/lib/core/monster.lua b/data/lib/core/monster.lua new file mode 100644 index 0000000..7624009 --- /dev/null +++ b/data/lib/core/monster.lua @@ -0,0 +1,27 @@ +function Monster.getMonster(self) + return self:isMonster() and self or nil +end + +function Monster.isItem(self) + return false +end + +function Monster.isMonster(self) + return true +end + +function Monster.isNpc(self) + return false +end + +function Monster.isPlayer(self) + return false +end + +function Monster.isTile(self) + return false +end + +function Monster.isContainer(self) + return false +end diff --git a/data/lib/core/player.lua b/data/lib/core/player.lua new file mode 100644 index 0000000..2e6d20a --- /dev/null +++ b/data/lib/core/player.lua @@ -0,0 +1,99 @@ +local foodCondition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + +function Player.feed(self, food) + local condition = self:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT) + if condition then + condition:setTicks(condition:getTicks() + (food * 1000)) + else + local vocation = self:getVocation() + if not vocation then + return nil + end + + foodCondition:setTicks(food * 1000) + foodCondition:setParameter(CONDITION_PARAM_HEALTHGAIN, vocation:getHealthGainAmount()) + foodCondition:setParameter(CONDITION_PARAM_HEALTHTICKS, vocation:getHealthGainTicks() * 1000) + foodCondition:setParameter(CONDITION_PARAM_MANAGAIN, vocation:getManaGainAmount()) + foodCondition:setParameter(CONDITION_PARAM_MANATICKS, vocation:getManaGainTicks() * 1000) + + self:addCondition(foodCondition) + end + return true +end + +function Player.getClosestFreePosition(self, position, extended) + if self:getAccountType() >= ACCOUNT_TYPE_GOD then + return position + end + return Creature.getClosestFreePosition(self, position, extended) +end + +function Player.getDepotItems(self, depotId) + return self:getDepotChest(depotId, true):getItemHoldingCount() +end + +function Player.isNoVocation(self) + return self:getVocation():getId() == 0 +end + +function Player.isSorcerer(self) + return self:getVocation():getId() == 1 or self:getVocation():getId() == 5 +end + +function Player.isDruid(self) + return self:getVocation():getId() == 2 or self:getVocation():getId() == 6 +end + +function Player.isPaladin(self) + return self:getVocation():getId() == 3 or self:getVocation():getId() == 7 +end + +function Player.isKnight(self) + return self:getVocation():getId() == 4 or self:getVocation():getId() == 8 +end + +function Player.isPremium(self) + return self:getPremiumDays() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM) +end + +function Player.sendCancelMessage(self, message) + if type(message) == "number" then + message = Game.getReturnMessage(message) + end + return self:sendTextMessage(MESSAGE_STATUS_SMALL, message) +end + +function Player.isUsingOtClient(self) + return self:getClient().os >= CLIENTOS_OTCLIENT_LINUX +end + +function Player.sendExtendedOpcode(self, opcode, buffer) + if not self:isUsingOtClient() then + return false + end + + local networkMessage = NetworkMessage() + networkMessage:addByte(0x32) + networkMessage:addByte(opcode) + networkMessage:addString(buffer) + networkMessage:sendToPlayer(self) + networkMessage:delete() + return true +end + +APPLY_SKILL_MULTIPLIER = true +local addSkillTriesFunc = Player.addSkillTries +function Player.addSkillTries(...) + APPLY_SKILL_MULTIPLIER = false + local ret = addSkillTriesFunc(...) + APPLY_SKILL_MULTIPLIER = true + return ret +end + +local addManaSpentFunc = Player.addManaSpent +function Player.addManaSpent(...) + APPLY_SKILL_MULTIPLIER = false + local ret = addManaSpentFunc(...) + APPLY_SKILL_MULTIPLIER = true + return ret +end diff --git a/data/lib/core/position.lua b/data/lib/core/position.lua new file mode 100644 index 0000000..9350d4d --- /dev/null +++ b/data/lib/core/position.lua @@ -0,0 +1,75 @@ +Position.directionOffset = { + [DIRECTION_NORTH] = {x = 0, y = -1}, + [DIRECTION_EAST] = {x = 1, y = 0}, + [DIRECTION_SOUTH] = {x = 0, y = 1}, + [DIRECTION_WEST] = {x = -1, y = 0}, + [DIRECTION_SOUTHWEST] = {x = -1, y = 1}, + [DIRECTION_SOUTHEAST] = {x = 1, y = 1}, + [DIRECTION_NORTHWEST] = {x = -1, y = -1}, + [DIRECTION_NORTHEAST] = {x = 1, y = -1} +} + +function Position:getNextPosition(direction, steps) + local offset = Position.directionOffset[direction] + if offset then + steps = steps or 1 + self.x = self.x + offset.x * steps + self.y = self.y + offset.y * steps + end +end + +function Position:moveUpstairs() + local isWalkable = function (position) + local tile = Tile(position) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then + return false + end + + local items = tile:getItems() + for i = 1, tile:getItemCount() do + local item = items[i] + local itemType = item:getType() + if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKSOLID) then + return false + end + end + return true + end + + local swap = function (lhs, rhs) + lhs.x, rhs.x = rhs.x, lhs.x + lhs.y, rhs.y = rhs.y, lhs.y + lhs.z, rhs.z = rhs.z, lhs.z + end + + self.z = self.z - 1 + + local defaultPosition = self + Position.directionOffset[DIRECTION_SOUTH] + if not isWalkable(defaultPosition) then + for direction = DIRECTION_NORTH, DIRECTION_NORTHEAST do + if direction == DIRECTION_SOUTH then + direction = DIRECTION_WEST + end + + local position = self + Position.directionOffset[direction] + if isWalkable(position) then + swap(self, position) + return self + end + end + end + swap(self, defaultPosition) + return self +end + +function Position:moveRel(x, y, z) + self.x = self.x + x + self.y = self.y + y + self.z = self.z + z + return self +end \ No newline at end of file diff --git a/data/lib/core/teleport.lua b/data/lib/core/teleport.lua new file mode 100644 index 0000000..8930268 --- /dev/null +++ b/data/lib/core/teleport.lua @@ -0,0 +1,3 @@ +function Teleport.isTeleport(self) + return true +end diff --git a/data/lib/core/tile.lua b/data/lib/core/tile.lua new file mode 100644 index 0000000..c254ad7 --- /dev/null +++ b/data/lib/core/tile.lua @@ -0,0 +1,23 @@ +function Tile.isItem(self) + return false +end + +function Tile.isContainer(self) + return false +end + +function Tile.isCreature(self) + return false +end + +function Tile.isPlayer(self) + return false +end + +function Tile.isTeleport(self) + return false +end + +function Tile.isTile(self) + return true +end diff --git a/data/lib/lib.lua b/data/lib/lib.lua new file mode 100644 index 0000000..5a0e2b5 --- /dev/null +++ b/data/lib/lib.lua @@ -0,0 +1,5 @@ +-- Core API functions implemented in Lua +dofile('data/lib/core/core.lua') + +-- Compatibility library for our old Lua API +dofile('data/lib/compat/compat.lua') diff --git a/data/monster/amazon.xml b/data/monster/amazon.xml new file mode 100644 index 0000000..369cf03 --- /dev/null +++ b/data/monster/amazon.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/ancientscarab.xml b/data/monster/ancientscarab.xml new file mode 100644 index 0000000..ff321b8 --- /dev/null +++ b/data/monster/ancientscarab.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/apocalypse.xml b/data/monster/apocalypse.xml new file mode 100644 index 0000000..06cdd5d --- /dev/null +++ b/data/monster/apocalypse.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/assassin.xml b/data/monster/assassin.xml new file mode 100644 index 0000000..c975917 --- /dev/null +++ b/data/monster/assassin.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/badger.xml b/data/monster/badger.xml new file mode 100644 index 0000000..660ab95 --- /dev/null +++ b/data/monster/badger.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bandit.xml b/data/monster/bandit.xml new file mode 100644 index 0000000..41ca276 --- /dev/null +++ b/data/monster/bandit.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/banshee.xml b/data/monster/banshee.xml new file mode 100644 index 0000000..0c7f33b --- /dev/null +++ b/data/monster/banshee.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bat.xml b/data/monster/bat.xml new file mode 100644 index 0000000..79980cd --- /dev/null +++ b/data/monster/bat.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bazir.xml b/data/monster/bazir.xml new file mode 100644 index 0000000..c27634c --- /dev/null +++ b/data/monster/bazir.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bear.xml b/data/monster/bear.xml new file mode 100644 index 0000000..128fcf6 --- /dev/null +++ b/data/monster/bear.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/behemoth.xml b/data/monster/behemoth.xml new file mode 100644 index 0000000..978989f --- /dev/null +++ b/data/monster/behemoth.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/beholder.xml b/data/monster/beholder.xml new file mode 100644 index 0000000..55f9b1c --- /dev/null +++ b/data/monster/beholder.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/blackknight.xml b/data/monster/blackknight.xml new file mode 100644 index 0000000..4959aad --- /dev/null +++ b/data/monster/blackknight.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/blacksheep.xml b/data/monster/blacksheep.xml new file mode 100644 index 0000000..1c0df71 --- /dev/null +++ b/data/monster/blacksheep.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bluedjinn.xml b/data/monster/bluedjinn.xml new file mode 100644 index 0000000..6be31d8 --- /dev/null +++ b/data/monster/bluedjinn.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bonebeast.xml b/data/monster/bonebeast.xml new file mode 100644 index 0000000..a8f6c9d --- /dev/null +++ b/data/monster/bonebeast.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/bug.xml b/data/monster/bug.xml new file mode 100644 index 0000000..977bdfc --- /dev/null +++ b/data/monster/bug.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/butterflyblue.xml b/data/monster/butterflyblue.xml new file mode 100644 index 0000000..cec7961 --- /dev/null +++ b/data/monster/butterflyblue.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/butterflypurple.xml b/data/monster/butterflypurple.xml new file mode 100644 index 0000000..75bfe9c --- /dev/null +++ b/data/monster/butterflypurple.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/butterflyred.xml b/data/monster/butterflyred.xml new file mode 100644 index 0000000..36f330a --- /dev/null +++ b/data/monster/butterflyred.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/butterflyyellow.xml b/data/monster/butterflyyellow.xml new file mode 100644 index 0000000..500a175 --- /dev/null +++ b/data/monster/butterflyyellow.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/carniphila.xml b/data/monster/carniphila.xml new file mode 100644 index 0000000..1cd51e9 --- /dev/null +++ b/data/monster/carniphila.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/caverat.xml b/data/monster/caverat.xml new file mode 100644 index 0000000..58694bb --- /dev/null +++ b/data/monster/caverat.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/centipede.xml b/data/monster/centipede.xml new file mode 100644 index 0000000..4963073 --- /dev/null +++ b/data/monster/centipede.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/chicken.xml b/data/monster/chicken.xml new file mode 100644 index 0000000..be0dacb --- /dev/null +++ b/data/monster/chicken.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/cobra.xml b/data/monster/cobra.xml new file mode 100644 index 0000000..a8e53f9 --- /dev/null +++ b/data/monster/cobra.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/crab.xml b/data/monster/crab.xml new file mode 100644 index 0000000..c196cdb --- /dev/null +++ b/data/monster/crab.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/crocodile.xml b/data/monster/crocodile.xml new file mode 100644 index 0000000..c525058 --- /dev/null +++ b/data/monster/crocodile.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/cryptshambler.xml b/data/monster/cryptshambler.xml new file mode 100644 index 0000000..09a1558 --- /dev/null +++ b/data/monster/cryptshambler.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/cyclops.xml b/data/monster/cyclops.xml new file mode 100644 index 0000000..8f529f4 --- /dev/null +++ b/data/monster/cyclops.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/darkmonk.xml b/data/monster/darkmonk.xml new file mode 100644 index 0000000..368e8f2 --- /dev/null +++ b/data/monster/darkmonk.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/deathslicer.xml b/data/monster/deathslicer.xml new file mode 100644 index 0000000..e159361 --- /dev/null +++ b/data/monster/deathslicer.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/deer.xml b/data/monster/deer.xml new file mode 100644 index 0000000..189e6d9 --- /dev/null +++ b/data/monster/deer.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/demodras.xml b/data/monster/demodras.xml new file mode 100644 index 0000000..74e8c35 --- /dev/null +++ b/data/monster/demodras.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/demon.xml b/data/monster/demon.xml new file mode 100644 index 0000000..0b11e02 --- /dev/null +++ b/data/monster/demon.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/demonskeleton.xml b/data/monster/demonskeleton.xml new file mode 100644 index 0000000..56fd428 --- /dev/null +++ b/data/monster/demonskeleton.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dharalion.xml b/data/monster/dharalion.xml new file mode 100644 index 0000000..8b2aec6 --- /dev/null +++ b/data/monster/dharalion.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dog.xml b/data/monster/dog.xml new file mode 100644 index 0000000..d2ac418 --- /dev/null +++ b/data/monster/dog.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dragon.xml b/data/monster/dragon.xml new file mode 100644 index 0000000..6555252 --- /dev/null +++ b/data/monster/dragon.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dragonlord.xml b/data/monster/dragonlord.xml new file mode 100644 index 0000000..b1d6b3a --- /dev/null +++ b/data/monster/dragonlord.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dwarf.xml b/data/monster/dwarf.xml new file mode 100644 index 0000000..448d85b --- /dev/null +++ b/data/monster/dwarf.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dwarfgeomancer.xml b/data/monster/dwarfgeomancer.xml new file mode 100644 index 0000000..638efcd --- /dev/null +++ b/data/monster/dwarfgeomancer.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dwarfguard.xml b/data/monster/dwarfguard.xml new file mode 100644 index 0000000..d15369c --- /dev/null +++ b/data/monster/dwarfguard.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dwarfsoldier.xml b/data/monster/dwarfsoldier.xml new file mode 100644 index 0000000..7f60603 --- /dev/null +++ b/data/monster/dwarfsoldier.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dworcfleshhunter.xml b/data/monster/dworcfleshhunter.xml new file mode 100644 index 0000000..305dbc4 --- /dev/null +++ b/data/monster/dworcfleshhunter.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dworcvenomsniper.xml b/data/monster/dworcvenomsniper.xml new file mode 100644 index 0000000..93b922a --- /dev/null +++ b/data/monster/dworcvenomsniper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/dworcvoodoomaster.xml b/data/monster/dworcvoodoomaster.xml new file mode 100644 index 0000000..1f6a63a --- /dev/null +++ b/data/monster/dworcvoodoomaster.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/efreet.xml b/data/monster/efreet.xml new file mode 100644 index 0000000..50cde4e --- /dev/null +++ b/data/monster/efreet.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/elderbeholder.xml b/data/monster/elderbeholder.xml new file mode 100644 index 0000000..e0d524f --- /dev/null +++ b/data/monster/elderbeholder.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/elephant.xml b/data/monster/elephant.xml new file mode 100644 index 0000000..4023b0c --- /dev/null +++ b/data/monster/elephant.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/elf.xml b/data/monster/elf.xml new file mode 100644 index 0000000..31304dc --- /dev/null +++ b/data/monster/elf.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/elfarcanist.xml b/data/monster/elfarcanist.xml new file mode 100644 index 0000000..e477512 --- /dev/null +++ b/data/monster/elfarcanist.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/elfscout.xml b/data/monster/elfscout.xml new file mode 100644 index 0000000..076dc0d --- /dev/null +++ b/data/monster/elfscout.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/evileye.xml b/data/monster/evileye.xml new file mode 100644 index 0000000..93d5449 --- /dev/null +++ b/data/monster/evileye.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/fernfang.xml b/data/monster/fernfang.xml new file mode 100644 index 0000000..be3eb77 --- /dev/null +++ b/data/monster/fernfang.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/ferumbras.xml b/data/monster/ferumbras.xml new file mode 100644 index 0000000..7ab9082 --- /dev/null +++ b/data/monster/ferumbras.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/firedevil.xml b/data/monster/firedevil.xml new file mode 100644 index 0000000..e1de407 --- /dev/null +++ b/data/monster/firedevil.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/fireelemental.xml b/data/monster/fireelemental.xml new file mode 100644 index 0000000..5f6d6a0 --- /dev/null +++ b/data/monster/fireelemental.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/flamethrower.xml b/data/monster/flamethrower.xml new file mode 100644 index 0000000..d0b0724 --- /dev/null +++ b/data/monster/flamethrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/flamingo.xml b/data/monster/flamingo.xml new file mode 100644 index 0000000..7136731 --- /dev/null +++ b/data/monster/flamingo.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/frosttroll.xml b/data/monster/frosttroll.xml new file mode 100644 index 0000000..9dacb8e --- /dev/null +++ b/data/monster/frosttroll.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/gamemaster.xml b/data/monster/gamemaster.xml new file mode 100644 index 0000000..d94cec5 --- /dev/null +++ b/data/monster/gamemaster.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/gargoyle.xml b/data/monster/gargoyle.xml new file mode 100644 index 0000000..a880bd6 --- /dev/null +++ b/data/monster/gargoyle.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/gazer.xml b/data/monster/gazer.xml new file mode 100644 index 0000000..3a1f63f --- /dev/null +++ b/data/monster/gazer.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/ghost.xml b/data/monster/ghost.xml new file mode 100644 index 0000000..e3def40 --- /dev/null +++ b/data/monster/ghost.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/ghoul.xml b/data/monster/ghoul.xml new file mode 100644 index 0000000..99309ee --- /dev/null +++ b/data/monster/ghoul.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/giantspider.xml b/data/monster/giantspider.xml new file mode 100644 index 0000000..dd4beba --- /dev/null +++ b/data/monster/giantspider.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/goblin.xml b/data/monster/goblin.xml new file mode 100644 index 0000000..32bdd0e --- /dev/null +++ b/data/monster/goblin.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/greendjinn.xml b/data/monster/greendjinn.xml new file mode 100644 index 0000000..c1d30ab --- /dev/null +++ b/data/monster/greendjinn.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/grorlam.xml b/data/monster/grorlam.xml new file mode 100644 index 0000000..aa2c6ef --- /dev/null +++ b/data/monster/grorlam.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/halloweenhare.xml b/data/monster/halloweenhare.xml new file mode 100644 index 0000000..92debae --- /dev/null +++ b/data/monster/halloweenhare.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/hero.xml b/data/monster/hero.xml new file mode 100644 index 0000000..a16b215 --- /dev/null +++ b/data/monster/hero.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/hornedfox.xml b/data/monster/hornedfox.xml new file mode 100644 index 0000000..0ebae5a --- /dev/null +++ b/data/monster/hornedfox.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/hunter.xml b/data/monster/hunter.xml new file mode 100644 index 0000000..c9cd7b8 --- /dev/null +++ b/data/monster/hunter.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/hyaena.xml b/data/monster/hyaena.xml new file mode 100644 index 0000000..2909d8e --- /dev/null +++ b/data/monster/hyaena.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/hydra.xml b/data/monster/hydra.xml new file mode 100644 index 0000000..78e21fe --- /dev/null +++ b/data/monster/hydra.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/illusion.xml b/data/monster/illusion.xml new file mode 100644 index 0000000..73ec371 --- /dev/null +++ b/data/monster/illusion.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/infernatil.xml b/data/monster/infernatil.xml new file mode 100644 index 0000000..79b43ab --- /dev/null +++ b/data/monster/infernatil.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/kongra.xml b/data/monster/kongra.xml new file mode 100644 index 0000000..bd070d2 --- /dev/null +++ b/data/monster/kongra.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/larva.xml b/data/monster/larva.xml new file mode 100644 index 0000000..8afad09 --- /dev/null +++ b/data/monster/larva.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/lich.xml b/data/monster/lich.xml new file mode 100644 index 0000000..856a238 --- /dev/null +++ b/data/monster/lich.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/lion.xml b/data/monster/lion.xml new file mode 100644 index 0000000..9a85589 --- /dev/null +++ b/data/monster/lion.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/lizardsentinel.xml b/data/monster/lizardsentinel.xml new file mode 100644 index 0000000..85d0008 --- /dev/null +++ b/data/monster/lizardsentinel.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/lizardsnakecharmer.xml b/data/monster/lizardsnakecharmer.xml new file mode 100644 index 0000000..5fb3c15 --- /dev/null +++ b/data/monster/lizardsnakecharmer.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/lizardtemplar.xml b/data/monster/lizardtemplar.xml new file mode 100644 index 0000000..191d5a0 --- /dev/null +++ b/data/monster/lizardtemplar.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/magicthrower.xml b/data/monster/magicthrower.xml new file mode 100644 index 0000000..c5e8b11 --- /dev/null +++ b/data/monster/magicthrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/marid.xml b/data/monster/marid.xml new file mode 100644 index 0000000..d0c0dcb --- /dev/null +++ b/data/monster/marid.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/merlkin.xml b/data/monster/merlkin.xml new file mode 100644 index 0000000..7c0d38d --- /dev/null +++ b/data/monster/merlkin.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/mimic.xml b/data/monster/mimic.xml new file mode 100644 index 0000000..8240888 --- /dev/null +++ b/data/monster/mimic.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/minotaur.xml b/data/monster/minotaur.xml new file mode 100644 index 0000000..3a9dcde --- /dev/null +++ b/data/monster/minotaur.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/minotaurarcher.xml b/data/monster/minotaurarcher.xml new file mode 100644 index 0000000..b0edc59 --- /dev/null +++ b/data/monster/minotaurarcher.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/minotaurguard.xml b/data/monster/minotaurguard.xml new file mode 100644 index 0000000..f169374 --- /dev/null +++ b/data/monster/minotaurguard.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/minotaurmage.xml b/data/monster/minotaurmage.xml new file mode 100644 index 0000000..95bc4a1 --- /dev/null +++ b/data/monster/minotaurmage.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/monk.xml b/data/monster/monk.xml new file mode 100644 index 0000000..ac8a8f7 --- /dev/null +++ b/data/monster/monk.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/monsters.xml b/data/monster/monsters.xml new file mode 100644 index 0000000..d844a6e --- /dev/null +++ b/data/monster/monsters.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/morgaroth.xml b/data/monster/morgaroth.xml new file mode 100644 index 0000000..1ea315d --- /dev/null +++ b/data/monster/morgaroth.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/mummy.xml b/data/monster/mummy.xml new file mode 100644 index 0000000..6e23acf --- /dev/null +++ b/data/monster/mummy.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/murius.xml b/data/monster/murius.xml new file mode 100644 index 0000000..752e188 --- /dev/null +++ b/data/monster/murius.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/necromancer.xml b/data/monster/necromancer.xml new file mode 100644 index 0000000..281f65f --- /dev/null +++ b/data/monster/necromancer.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/necropharus.xml b/data/monster/necropharus.xml new file mode 100644 index 0000000..02bc6bb --- /dev/null +++ b/data/monster/necropharus.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/oldwidow.xml b/data/monster/oldwidow.xml new file mode 100644 index 0000000..b99e648 --- /dev/null +++ b/data/monster/oldwidow.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orc.xml b/data/monster/orc.xml new file mode 100644 index 0000000..9ece909 --- /dev/null +++ b/data/monster/orc.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcberserker.xml b/data/monster/orcberserker.xml new file mode 100644 index 0000000..5ea4676 --- /dev/null +++ b/data/monster/orcberserker.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcleader.xml b/data/monster/orcleader.xml new file mode 100644 index 0000000..f4d5c9c --- /dev/null +++ b/data/monster/orcleader.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcrider.xml b/data/monster/orcrider.xml new file mode 100644 index 0000000..008133c --- /dev/null +++ b/data/monster/orcrider.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcshaman.xml b/data/monster/orcshaman.xml new file mode 100644 index 0000000..ecf0645 --- /dev/null +++ b/data/monster/orcshaman.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcspearman.xml b/data/monster/orcspearman.xml new file mode 100644 index 0000000..8499911 --- /dev/null +++ b/data/monster/orcspearman.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcwarlord.xml b/data/monster/orcwarlord.xml new file mode 100644 index 0000000..6fe7c36 --- /dev/null +++ b/data/monster/orcwarlord.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orcwarrior.xml b/data/monster/orcwarrior.xml new file mode 100644 index 0000000..c576cb6 --- /dev/null +++ b/data/monster/orcwarrior.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/orshabaal.xml b/data/monster/orshabaal.xml new file mode 100644 index 0000000..90a1543 --- /dev/null +++ b/data/monster/orshabaal.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/panda.xml b/data/monster/panda.xml new file mode 100644 index 0000000..91af8b3 --- /dev/null +++ b/data/monster/panda.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/parrot.xml b/data/monster/parrot.xml new file mode 100644 index 0000000..8f0f75e --- /dev/null +++ b/data/monster/parrot.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohashmunrah.xml b/data/monster/pharaohashmunrah.xml new file mode 100644 index 0000000..4e7e077 --- /dev/null +++ b/data/monster/pharaohashmunrah.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohdipthrah.xml b/data/monster/pharaohdipthrah.xml new file mode 100644 index 0000000..0c8c90b --- /dev/null +++ b/data/monster/pharaohdipthrah.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohmahrdis.xml b/data/monster/pharaohmahrdis.xml new file mode 100644 index 0000000..4d7df10 --- /dev/null +++ b/data/monster/pharaohmahrdis.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohmorguthis.xml b/data/monster/pharaohmorguthis.xml new file mode 100644 index 0000000..e7cc7f8 --- /dev/null +++ b/data/monster/pharaohmorguthis.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohomruc.xml b/data/monster/pharaohomruc.xml new file mode 100644 index 0000000..c42e56b --- /dev/null +++ b/data/monster/pharaohomruc.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohrahemos.xml b/data/monster/pharaohrahemos.xml new file mode 100644 index 0000000..eccad9a --- /dev/null +++ b/data/monster/pharaohrahemos.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohthalas.xml b/data/monster/pharaohthalas.xml new file mode 100644 index 0000000..b3ec301 --- /dev/null +++ b/data/monster/pharaohthalas.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pharaohvashresamun.xml b/data/monster/pharaohvashresamun.xml new file mode 100644 index 0000000..717fc37 --- /dev/null +++ b/data/monster/pharaohvashresamun.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/pig.xml b/data/monster/pig.xml new file mode 100644 index 0000000..007ecee --- /dev/null +++ b/data/monster/pig.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/plaguethrower.xml b/data/monster/plaguethrower.xml new file mode 100644 index 0000000..5eadcd1 --- /dev/null +++ b/data/monster/plaguethrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/poisonspider.xml b/data/monster/poisonspider.xml new file mode 100644 index 0000000..dc7469b --- /dev/null +++ b/data/monster/poisonspider.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/polarbear.xml b/data/monster/polarbear.xml new file mode 100644 index 0000000..f2df136 --- /dev/null +++ b/data/monster/polarbear.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/priestess.xml b/data/monster/priestess.xml new file mode 100644 index 0000000..b62c034 --- /dev/null +++ b/data/monster/priestess.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/rabbit.xml b/data/monster/rabbit.xml new file mode 100644 index 0000000..864f7de --- /dev/null +++ b/data/monster/rabbit.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/raids/orc.xml b/data/monster/raids/orc.xml new file mode 100644 index 0000000..4a39e22 --- /dev/null +++ b/data/monster/raids/orc.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/raids/orcwarlord.xml b/data/monster/raids/orcwarlord.xml new file mode 100644 index 0000000..3ea64a1 --- /dev/null +++ b/data/monster/raids/orcwarlord.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/rat.xml b/data/monster/rat.xml new file mode 100644 index 0000000..6ddf041 --- /dev/null +++ b/data/monster/rat.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/rotworm.xml b/data/monster/rotworm.xml new file mode 100644 index 0000000..bcbca07 --- /dev/null +++ b/data/monster/rotworm.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/scarab.xml b/data/monster/scarab.xml new file mode 100644 index 0000000..e650c89 --- /dev/null +++ b/data/monster/scarab.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/scorpion.xml b/data/monster/scorpion.xml new file mode 100644 index 0000000..d0417ec --- /dev/null +++ b/data/monster/scorpion.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/serpentspawn.xml b/data/monster/serpentspawn.xml new file mode 100644 index 0000000..8966730 --- /dev/null +++ b/data/monster/serpentspawn.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/sheep.xml b/data/monster/sheep.xml new file mode 100644 index 0000000..c64d20f --- /dev/null +++ b/data/monster/sheep.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/shredderthrower.xml b/data/monster/shredderthrower.xml new file mode 100644 index 0000000..34051c3 --- /dev/null +++ b/data/monster/shredderthrower.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/sibang.xml b/data/monster/sibang.xml new file mode 100644 index 0000000..5437069 --- /dev/null +++ b/data/monster/sibang.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/skeleton.xml b/data/monster/skeleton.xml new file mode 100644 index 0000000..d553574 --- /dev/null +++ b/data/monster/skeleton.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/skunk.xml b/data/monster/skunk.xml new file mode 100644 index 0000000..f0fb197 --- /dev/null +++ b/data/monster/skunk.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/slime.xml b/data/monster/slime.xml new file mode 100644 index 0000000..ca7e4a5 --- /dev/null +++ b/data/monster/slime.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/slime2.xml b/data/monster/slime2.xml new file mode 100644 index 0000000..868d95c --- /dev/null +++ b/data/monster/slime2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/smuggler.xml b/data/monster/smuggler.xml new file mode 100644 index 0000000..d7b27b6 --- /dev/null +++ b/data/monster/smuggler.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/snake.xml b/data/monster/snake.xml new file mode 100644 index 0000000..c541119 --- /dev/null +++ b/data/monster/snake.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/spider.xml b/data/monster/spider.xml new file mode 100644 index 0000000..7fe60fa --- /dev/null +++ b/data/monster/spider.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/spitnettle.xml b/data/monster/spitnettle.xml new file mode 100644 index 0000000..45c3af5 --- /dev/null +++ b/data/monster/spitnettle.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/stalker.xml b/data/monster/stalker.xml new file mode 100644 index 0000000..e65b31b --- /dev/null +++ b/data/monster/stalker.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/stonegolem.xml b/data/monster/stonegolem.xml new file mode 100644 index 0000000..3a8e37c --- /dev/null +++ b/data/monster/stonegolem.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/swamptroll.xml b/data/monster/swamptroll.xml new file mode 100644 index 0000000..8a503cd --- /dev/null +++ b/data/monster/swamptroll.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/tarantula.xml b/data/monster/tarantula.xml new file mode 100644 index 0000000..dc03634 --- /dev/null +++ b/data/monster/tarantula.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/terrorbird.xml b/data/monster/terrorbird.xml new file mode 100644 index 0000000..6d40b1f --- /dev/null +++ b/data/monster/terrorbird.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/tiger.xml b/data/monster/tiger.xml new file mode 100644 index 0000000..ebc2bbd --- /dev/null +++ b/data/monster/tiger.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/troll.xml b/data/monster/troll.xml new file mode 100644 index 0000000..4ed5443 --- /dev/null +++ b/data/monster/troll.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/valkyrie.xml b/data/monster/valkyrie.xml new file mode 100644 index 0000000..7b6e1f6 --- /dev/null +++ b/data/monster/valkyrie.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/vampire.xml b/data/monster/vampire.xml new file mode 100644 index 0000000..d40a558 --- /dev/null +++ b/data/monster/vampire.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/warlock.xml b/data/monster/warlock.xml new file mode 100644 index 0000000..8f2a2d7 --- /dev/null +++ b/data/monster/warlock.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/warwolf.xml b/data/monster/warwolf.xml new file mode 100644 index 0000000..3540496 --- /dev/null +++ b/data/monster/warwolf.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/wasp.xml b/data/monster/wasp.xml new file mode 100644 index 0000000..3727f65 --- /dev/null +++ b/data/monster/wasp.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/wildwarrior.xml b/data/monster/wildwarrior.xml new file mode 100644 index 0000000..ace60df --- /dev/null +++ b/data/monster/wildwarrior.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/winterwolf.xml b/data/monster/winterwolf.xml new file mode 100644 index 0000000..cb81ea1 --- /dev/null +++ b/data/monster/winterwolf.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/witch.xml b/data/monster/witch.xml new file mode 100644 index 0000000..a725ba6 --- /dev/null +++ b/data/monster/witch.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/wolf.xml b/data/monster/wolf.xml new file mode 100644 index 0000000..325f663 --- /dev/null +++ b/data/monster/wolf.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/monster/yeti.xml b/data/monster/yeti.xml new file mode 100644 index 0000000..6b9e370 --- /dev/null +++ b/data/monster/yeti.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/movements/lib/movements.lua b/data/movements/lib/movements.lua new file mode 100644 index 0000000..b081a0f --- /dev/null +++ b/data/movements/lib/movements.lua @@ -0,0 +1,2 @@ +-- Nothing -- + diff --git a/data/movements/movements.xml b/data/movements/movements.xml new file mode 100644 index 0000000..41b6aa3 --- /dev/null +++ b/data/movements/movements.xml @@ -0,0 +1,697 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/movements/scripts/misc/candelabrum.lua b/data/movements/scripts/misc/candelabrum.lua new file mode 100644 index 0000000..dc7ba47 --- /dev/null +++ b/data/movements/scripts/misc/candelabrum.lua @@ -0,0 +1,7 @@ +function onRemoveItem(item, tileitem, position) + if item:getPosition():getDistance(position) > 0 then + item:transform(2912, 1) + item:decay() + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/damage.lua b/data/movements/scripts/misc/damage.lua new file mode 100644 index 0000000..fa0c4d1 --- /dev/null +++ b/data/movements/scripts/misc/damage.lua @@ -0,0 +1,26 @@ +function onStepIn(creature, item, position, fromPosition) + local tile = Tile(position) + if tile:hasFlag(TILESTATE_PROTECTIONZONE) then + return + end + + if item:getId() == 2145 then + item:transform(2146, 1) + item:decay() + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -60, -60) + elseif item:getId() == 2146 or item:getId() == 2148 then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -60, -60) + elseif item:getId() == 3482 then + if not creature:isPlayer() then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -30, -30) + else + position:sendMagicEffect(CONST_ME_POFF) + end + item:transform(3481, 1) + item:decay() + elseif item:getId() == 3944 then + doTargetCombatHealth(0, creature, COMBAT_PHYSICALDAMAGE, -30, -30) + item:transform(3945, 1) + item:decay() + end +end diff --git a/data/movements/scripts/misc/depot_switch.lua b/data/movements/scripts/misc/depot_switch.lua new file mode 100644 index 0000000..2966946 --- /dev/null +++ b/data/movements/scripts/misc/depot_switch.lua @@ -0,0 +1,36 @@ +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local player = Player(creature) + local lookPosition = player:getPosition() + lookPosition:getNextPosition(player:getDirection()) + local depotItem = Tile(lookPosition):getItemByType(ITEM_TYPE_DEPOT) + if depotItem ~= nil then + local depotItems = player:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount() + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or ".")) + end + + if item:getId() == 431 then + item:transform(430) + elseif item:getId() == 419 then + item:transform(420) + elseif item:getId() == 452 then + item:transform(453) + elseif item:getId() == 563 then + item:transform(564) + end +end + +function onStepOut(creature, item, position, fromPosition) + if item:getId() == 430 then + item:transform(431) + elseif item:getId() == 420 then + item:transform(419) + elseif item:getId() == 453 then + item:transform(452) + elseif item:getId() == 564 then + item:transform(563) + end +end diff --git a/data/movements/scripts/misc/doors.lua b/data/movements/scripts/misc/doors.lua new file mode 100644 index 0000000..9667f42 --- /dev/null +++ b/data/movements/scripts/misc/doors.lua @@ -0,0 +1,39 @@ +local verticalDoors = { + [1643] = 1642, + [1647] = 1646, + [1661] = 1660, + [1665] = 1664, + [1675] = 1674, + [1679] = 1678, + [1697] = 1696, + [1699] = 1698, +} + +local horizontalDoors = { + [1645] = 1644, + [1649] = 1648, + [1663] = 1662, + [1667] = 1666, + [1677] = 1676, + [1681] = 1680, + [1688] = 1687, + [1690] = 1689, +} + +function onStepOut(creature, item, fromPosition, toPosition) + local door = verticalDoors[item:getId()] + if door then + doRelocate(item:getPosition(), item:getPosition():moveRel(1, 0, 0)) + item:transform(door) + item:decay() + return true + end + + door = horizontalDoors[item:getId()] + if door then + doRelocate(item:getPosition(), item:getPosition():moveRel(0, 1, 0)) + item:transform(door) + item:decay() + return true + end +end \ No newline at end of file diff --git a/data/movements/scripts/misc/dustbin.lua b/data/movements/scripts/misc/dustbin.lua new file mode 100644 index 0000000..9527c1c --- /dev/null +++ b/data/movements/scripts/misc/dustbin.lua @@ -0,0 +1,4 @@ +function onAddItem(item, tileitem, position) + item:remove() + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/floorchange.lua b/data/movements/scripts/misc/floorchange.lua new file mode 100644 index 0000000..0362685 --- /dev/null +++ b/data/movements/scripts/misc/floorchange.lua @@ -0,0 +1,105 @@ +local list = { + [293] = {x = 0, y = 0, z = 1}, + [294] = {x = 0, y = 0, z = 1}, + [369] = {x = 0, y = 0, z = 1}, + [370] = {x = 0, y = 0, z = 1}, + [385] = {x = 0, y = 0, z = 1}, + [394] = {x = 0, y = 0, z = 1}, + [411] = {x = 0, y = 0, z = 1}, + [412] = {x = 0, y = 0, z = 1}, + [413] = {x = 0, y = 1, z = 1}, + [414] = {x = 0, y = 1, z = 1}, + [428] = {x = 0, y = 1, z = 1}, + [4823] = {x = 0, y = 1, z = 1}, + [4824] = {x = 0, y = 1, z = 1}, + [4825] = {x = 0, y = 1, z = 1}, + [4826] = {x = 0, y = 1, z = 1}, + [432] = {x = 0, y = 0, z = 1}, + [433] = {x = 0, y = 0, z = 1}, + [434] = {x = 0, y = 1, z = 1}, + [437] = {x = 0, y = 1, z = 1}, + [438] = {x = 0, y = 1, z = 1}, + [451] = {x = 0, y = 1, z = 1}, + [465] = {x = 0, y = -1, z = 1}, + [466] = {x = -1, y = 0, z = 1}, + [467] = {x = 1, y = 0, z = 1}, + [471] = {x = -1, y = -1, z = 1}, + [472] = {x = 1, y = -1, z = 1}, + [473] = {x = -1, y = 1, z = 1}, + [474] = {x = 1, y = 1, z = 1}, + [475] = {x = 0, y = 0, z = 1}, + [476] = {x = 0, y = 0, z = 1}, + [482] = {x = 0, y = 0, z = 1}, + [5081] = {x = 0, y = 0, z = 1}, + [483] = {x = 0, y = 0, z = 1}, + [484] = {x = 0, y = 1, z = 1}, + [485] = {x = 0, y = 1, z = 1}, + [566] = {x = 0, y = 1, z = 1}, + [567] = {x = 1, y = 0, z = 1}, + [594] = {x = 0, y = 0, z = 1}, + [595] = {x = 0, y = 0, z = 1}, + [600] = {x = -1, y = 0, z = 1}, + [601] = {x = 1, y = 0, z = 1}, + [604] = {x = -1, y = 0, z = 1}, + [605] = {x = 1, y = 0, z = 1}, + [607] = {x = 0, y = 0, z = 1}, + [609] = {x = 0, y = 0, z = 1}, + [610] = {x = 0, y = 0, z = 1}, + [615] = {x = 0, y = 0, z = 1}, + [1066] = {x = 0, y = 0, z = 1}, + [1067] = {x = 0, y = 0, z = 1}, + [1080] = {x = 0, y = 0, z = 1}, + [1156] = {x = 0, y = 1, z = 1}, + [1947] = {x = 0, y = -1, z = -1}, + [1950] = {x = 1, y = 0, z = -1}, + [1952] = {x = -1, y = 0, z = -1}, + [1954] = {x = 0, y = 1, z = -1}, + [1956] = {x = 0, y = -1, z = -1}, + [1958] = {x = 0, y = -1, z = -1}, + [1960] = {x = 1, y = 0, z = -1}, + [1962] = {x = -1, y = 0, z = -1}, + [1964] = {x = 0, y = 1, z = -1}, + [1966] = {x = 0, y = -1, z = -1}, + [1969] = {x = 1, y = 0, z = -1}, + [1971] = {x = -1, y = 0, z = -1}, + [1973] = {x = 0, y = 1, z = -1}, + [1975] = {x = 0, y = -1, z = -1}, + [1977] = {x = 0, y = -1, z = -1}, + [1978] = {x = -1, y = 0, z = -1}, + [2192] = {x = -1, y = -1, z = -1}, + [2194] = {x = 1, y = -1, z = -1}, + [2196] = {x = 1, y = 1, z = -1}, + [2198] = {x = -1, y = 1, z = -1}, +} + +function onStepIn(creature, item, position, fromPosition) + local entry = list[item:getId()] + local relPos = item:getPosition():moveRel(entry.x, entry.y, entry.z) + + local tile = Tile(relPos) + if tile == nil or tile:getGround() == nil then + return false + end + + creature:teleportTo(relPos) + if item:getId() == 293 then + item:transform(294) + item:decay() + elseif item:getId() == 475 then + item:transform(476) + item:decay() + elseif item:getId() == 1066 then + item:transform(1067) + item:decay() + end + return true +end + +function onAddItem(item, tileitem, position) + if tileitem:getId() ~= 293 and tileitem:getId() ~= 475 and tileitem:getId() ~= 476 and tileitem:getId() ~= 1066 then + local entry = list[tileitem:getId()] + local relPos = tileitem:getPosition():moveRel(entry.x, entry.y, entry.z) + item:moveTo(relPos) + end + return true +end diff --git a/data/movements/scripts/misc/lava.lua b/data/movements/scripts/misc/lava.lua new file mode 100644 index 0000000..d7854df --- /dev/null +++ b/data/movements/scripts/misc/lava.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(16) + item:remove() + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/open_trap.lua b/data/movements/scripts/misc/open_trap.lua new file mode 100644 index 0000000..1f0e3f2 --- /dev/null +++ b/data/movements/scripts/misc/open_trap.lua @@ -0,0 +1,8 @@ +function onRemoveItem(item, tileitem, position) + if item:getPosition():getDistance(position) > 0 then + item:transform(3481, 1) + item:decay() + item:getPosition():sendMagicEffect(3) + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/sandstone_wall.lua b/data/movements/scripts/misc/sandstone_wall.lua new file mode 100644 index 0000000..80a97e7 --- /dev/null +++ b/data/movements/scripts/misc/sandstone_wall.lua @@ -0,0 +1,39 @@ +function onStepIn(creature, item, fromPosition, toPosition) + if item:getId() == 478 then + item:transform(479, 1) + item:decay() + elseif item:getId() == 480 then + item:transform(481, 1) + item:decay() + end +end + +function onStepOut(creature, item, fromPosition, toPosition) + if item:getId() == 479 then + item:transform(478, 1) + item:decay() + elseif item:getId() == 481 then + item:transform(480, 1) + item:decay() + end +end + +function onAddItem(item, tileitem, position) + if tileitem:getId() == 478 then + tileitem:transform(479, 1) + tileitem:decay() + elseif tileitem:getId() == 480 then + tileitem:transform(481, 1) + tileitem:decay() + end +end + +function onRemoveItem(item, tileitem, position) + if tileitem:getId() == 479 then + tileitem:transform(478, 1) + tileitem:decay() + elseif tileitem:getId() == 481 then + tileitem:transform(480, 1) + tileitem:decay() + end +end \ No newline at end of file diff --git a/data/movements/scripts/misc/swamp.lua b/data/movements/scripts/misc/swamp.lua new file mode 100644 index 0000000..f8ae678 --- /dev/null +++ b/data/movements/scripts/misc/swamp.lua @@ -0,0 +1,8 @@ +function onAddItem(item, tileitem, position) + if (item:getType():isMovable() and Tile(position):getThingCount() == 2) or + (tileitem:getId() >= 4874 and tileitem:getId() <= 4880) then + item:getPosition():sendMagicEffect(9) + item:remove() + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/tar.lua b/data/movements/scripts/misc/tar.lua new file mode 100644 index 0000000..5f8c0d9 --- /dev/null +++ b/data/movements/scripts/misc/tar.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(3) + item:remove() + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/misc/water.lua b/data/movements/scripts/misc/water.lua new file mode 100644 index 0000000..65420e1 --- /dev/null +++ b/data/movements/scripts/misc/water.lua @@ -0,0 +1,7 @@ +function onAddItem(item, tileitem, position) + if item:getType():isMovable() and Tile(position):getThingCount() == 2 then + item:getPosition():sendMagicEffect(2) + item:remove() + end + return true +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/1.lua b/data/movements/scripts/nostalrius/1.lua new file mode 100644 index 0000000..2966946 --- /dev/null +++ b/data/movements/scripts/nostalrius/1.lua @@ -0,0 +1,36 @@ +function onStepIn(creature, item, position, fromPosition) + if not creature:isPlayer() then + return true + end + + local player = Player(creature) + local lookPosition = player:getPosition() + lookPosition:getNextPosition(player:getDirection()) + local depotItem = Tile(lookPosition):getItemByType(ITEM_TYPE_DEPOT) + if depotItem ~= nil then + local depotItems = player:getDepotChest(getDepotId(depotItem:getUniqueId()), true):getItemHoldingCount() + player:sendTextMessage(MESSAGE_STATUS_DEFAULT, "Your depot contains " .. depotItems .. " item" .. (depotItems > 1 and "s." or ".")) + end + + if item:getId() == 431 then + item:transform(430) + elseif item:getId() == 419 then + item:transform(420) + elseif item:getId() == 452 then + item:transform(453) + elseif item:getId() == 563 then + item:transform(564) + end +end + +function onStepOut(creature, item, position, fromPosition) + if item:getId() == 430 then + item:transform(431) + elseif item:getId() == 420 then + item:transform(419) + elseif item:getId() == 453 then + item:transform(452) + elseif item:getId() == 564 then + item:transform(563) + end +end diff --git a/data/movements/scripts/nostalrius/10.lua b/data/movements/scripts/nostalrius/10.lua new file mode 100644 index 0000000..f9f8ada --- /dev/null +++ b/data/movements/scripts/nostalrius/10.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32266, y = 31916, z = 12}, 394) then + Game.transformItemOnMap({x = 32266, y = 31916, z = 12}, 394, 372) + end +end diff --git a/data/movements/scripts/nostalrius/100.lua b/data/movements/scripts/nostalrius/100.lua new file mode 100644 index 0000000..74d5d6a --- /dev/null +++ b/data/movements/scripts/nostalrius/100.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3239) >= 1 and creature:getPlayer():getStorageValue(267) == 0 then + doRelocate(item:getPosition(),{x = 33178, y = 33016, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33178, y = 33016, z = 14}, 11) + creature:getPlayer():removeItem(3239, 1) + else + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/101.lua b/data/movements/scripts/nostalrius/101.lua new file mode 100644 index 0000000..264dd74 --- /dev/null +++ b/data/movements/scripts/nostalrius/101.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/102.lua b/data/movements/scripts/nostalrius/102.lua new file mode 100644 index 0000000..a07d6dc --- /dev/null +++ b/data/movements/scripts/nostalrius/102.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33179, y = 32880, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32880, z = 11}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33179, y = 32880, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32880, z = 11}, 11) +end diff --git a/data/movements/scripts/nostalrius/103.lua b/data/movements/scripts/nostalrius/103.lua new file mode 100644 index 0000000..9657677 --- /dev/null +++ b/data/movements/scripts/nostalrius/103.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3240) >= 1 and creature:getPlayer():getStorageValue(261) == 0 then + doRelocate(item:getPosition(),{x = 33174, y = 32937, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33174, y = 32937, z = 15}, 11) + creature:getPlayer():removeItem(3240, 1) + else + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/104.lua b/data/movements/scripts/nostalrius/104.lua new file mode 100644 index 0000000..3888fb5 --- /dev/null +++ b/data/movements/scripts/nostalrius/104.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33158, y = 32771, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33158, y = 32771, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33158, y = 32771, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33158, y = 32771, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/105.lua b/data/movements/scripts/nostalrius/105.lua new file mode 100644 index 0000000..b06d4d5 --- /dev/null +++ b/data/movements/scripts/nostalrius/105.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33190, y = 32947, z = 15}) + Game.sendMagicEffect({x = 33183, y = 32757, z = 15}, 11) + Game.sendMagicEffect({x = 33191, y = 32947, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33190, y = 32947, z = 15}) + Game.sendMagicEffect({x = 33183, y = 32757, z = 15}, 11) + Game.sendMagicEffect({x = 33191, y = 32947, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/106.lua b/data/movements/scripts/nostalrius/106.lua new file mode 100644 index 0000000..e529b2c --- /dev/null +++ b/data/movements/scripts/nostalrius/106.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/107.lua b/data/movements/scripts/nostalrius/107.lua new file mode 100644 index 0000000..88dba55 --- /dev/null +++ b/data/movements/scripts/nostalrius/107.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/108.lua b/data/movements/scripts/nostalrius/108.lua new file mode 100644 index 0000000..71f5b0d --- /dev/null +++ b/data/movements/scripts/nostalrius/108.lua @@ -0,0 +1,23 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33176, y = 32880, z = 11},2570) and Game.isItemThere ({x = 33175, y = 32884, z = 11},2570) and Game.isItemThere ({x = 33176, y = 32889, z = 11},2570) and Game.isItemThere ({x = 33182, y = 32880, z = 11},2570) and Game.isItemThere ({x = 33183, y = 32884, z = 11},2570) and Game.isItemThere ({x = 33181, y = 32889, z = 11}, 2570) then + doRelocate(item:getPosition(),{x = 33198, y = 32885, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33198, y = 32885, z = 11}, 11) + Game.transformItemOnMap({x = 33176, y = 32880, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33175, y = 32884, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33176, y = 32889, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33182, y = 32880, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33183, y = 32884, z = 11}, 2570, 2569) + Game.transformItemOnMap({x = 33181, y = 32889, z = 11}, 2570, 2569) + else + doRelocate(item:getPosition(),{x = 33179, y = 32889, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32889, z = 11}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33179, y = 32889, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33179, y = 32889, z = 11}, 11) +end diff --git a/data/movements/scripts/nostalrius/109.lua b/data/movements/scripts/nostalrius/109.lua new file mode 100644 index 0000000..ba7d1bb --- /dev/null +++ b/data/movements/scripts/nostalrius/109.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33128, y = 32656, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33128, y = 32656, z = 15}, 11) + creature:getPlayer():setStorageValue(259,0) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33128, y = 32656, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33128, y = 32656, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/11.lua b/data/movements/scripts/nostalrius/11.lua new file mode 100644 index 0000000..b5ec0be --- /dev/null +++ b/data/movements/scripts/nostalrius/11.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32266, y = 31892, z = 12}, 394) then + Game.transformItemOnMap({x = 32266, y = 31892, z = 12}, 394, 372) + end +end diff --git a/data/movements/scripts/nostalrius/110.lua b/data/movements/scripts/nostalrius/110.lua new file mode 100644 index 0000000..4c6d59c --- /dev/null +++ b/data/movements/scripts/nostalrius/110.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/111.lua b/data/movements/scripts/nostalrius/111.lua new file mode 100644 index 0000000..601ca22 --- /dev/null +++ b/data/movements/scripts/nostalrius/111.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3237) >= 1 and creature:getPlayer():getStorageValue(263) == 0 then + doRelocate(item:getPosition(),{x = 33182, y = 32715, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33182, y = 32715, z = 14}, 11) + creature:getPlayer():removeItem(3237, 1) + else + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/112.lua b/data/movements/scripts/nostalrius/112.lua new file mode 100644 index 0000000..4f07d47 --- /dev/null +++ b/data/movements/scripts/nostalrius/112.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33149, y = 32870, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33149, y = 32870, z = 11}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33149, y = 32870, z = 11}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33149, y = 32870, z = 11}, 11) +end diff --git a/data/movements/scripts/nostalrius/113.lua b/data/movements/scripts/nostalrius/113.lua new file mode 100644 index 0000000..c2d2a40 --- /dev/null +++ b/data/movements/scripts/nostalrius/113.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32839, z = 09}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32839, z = 09}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32839, z = 09}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32839, z = 09}, 11) +end diff --git a/data/movements/scripts/nostalrius/114.lua b/data/movements/scripts/nostalrius/114.lua new file mode 100644 index 0000000..e4aae80 --- /dev/null +++ b/data/movements/scripts/nostalrius/114.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33147, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33147, y = 32864, z = 07}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33147, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33147, y = 32864, z = 07}, 15) +end diff --git a/data/movements/scripts/nostalrius/115.lua b/data/movements/scripts/nostalrius/115.lua new file mode 100644 index 0000000..634d341 --- /dev/null +++ b/data/movements/scripts/nostalrius/115.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/116.lua b/data/movements/scripts/nostalrius/116.lua new file mode 100644 index 0000000..33bc981 --- /dev/null +++ b/data/movements/scripts/nostalrius/116.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33145, y = 32862, z = 07}, 3465) and creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33151, y = 32864, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33151, y = 32864, z = 07}, 15) + else + doRelocate(item:getPosition(),{x = 33145, y = 32863, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33145, y = 32863, z = 07}, 15) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33145, y = 32863, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33145, y = 32863, z = 07}, 15) +end diff --git a/data/movements/scripts/nostalrius/117.lua b/data/movements/scripts/nostalrius/117.lua new file mode 100644 index 0000000..264dd74 --- /dev/null +++ b/data/movements/scripts/nostalrius/117.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/118.lua b/data/movements/scripts/nostalrius/118.lua new file mode 100644 index 0000000..09f8436 --- /dev/null +++ b/data/movements/scripts/nostalrius/118.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33125, y = 32760, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33125, y = 32760, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33125, y = 32760, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33125, y = 32760, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/119.lua b/data/movements/scripts/nostalrius/119.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/data/movements/scripts/nostalrius/119.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/12.lua b/data/movements/scripts/nostalrius/12.lua new file mode 100644 index 0000000..221ed60 --- /dev/null +++ b/data/movements/scripts/nostalrius/12.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32266, y = 31898, z = 12},{x = 32266, y = 31886, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/120.lua b/data/movements/scripts/nostalrius/120.lua new file mode 100644 index 0000000..e2e9cad --- /dev/null +++ b/data/movements/scripts/nostalrius/120.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + Game.sendMagicEffect({x = 33124, y = 32759, z = 14}, 11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + Game.sendMagicEffect({x = 33124, y = 32759, z = 14}, 11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/121.lua b/data/movements/scripts/nostalrius/121.lua new file mode 100644 index 0000000..89302bb --- /dev/null +++ b/data/movements/scripts/nostalrius/121.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33085, y = 32781, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33085, y = 32781, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33085, y = 32781, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33085, y = 32781, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/122.lua b/data/movements/scripts/nostalrius/122.lua new file mode 100644 index 0000000..fc4932c --- /dev/null +++ b/data/movements/scripts/nostalrius/122.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33193, y = 32664, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33193, y = 32664, z = 15}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33193, y = 32664, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33193, y = 32664, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/123.lua b/data/movements/scripts/nostalrius/123.lua new file mode 100644 index 0000000..ce78d67 --- /dev/null +++ b/data/movements/scripts/nostalrius/123.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3236) >= 1 and creature:getPlayer():getStorageValue(264) == 0 then + doRelocate(item:getPosition(),{x = 33146, y = 32666, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33146, y = 32666, z = 15}, 11) + creature:getPlayer():removeItem(3236, 1) + else + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32592, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32592, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/124.lua b/data/movements/scripts/nostalrius/124.lua new file mode 100644 index 0000000..805f0c1 --- /dev/null +++ b/data/movements/scripts/nostalrius/124.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33204, y = 32956, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33204, y = 32956, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33204, y = 32956, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33204, y = 32956, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/125.lua b/data/movements/scripts/nostalrius/125.lua new file mode 100644 index 0000000..36bba3a --- /dev/null +++ b/data/movements/scripts/nostalrius/125.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3241) >= 1 and creature:getPlayer():getStorageValue(265) == 0 then + doRelocate(item:getPosition(),{x = 33126, y = 32592, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33126, y = 32591, z = 15}, 11) + creature:getPlayer():removeItem(3241, 1) + else + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/126.lua b/data/movements/scripts/nostalrius/126.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/data/movements/scripts/nostalrius/126.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/127.lua b/data/movements/scripts/nostalrius/127.lua new file mode 100644 index 0000000..8b2283c --- /dev/null +++ b/data/movements/scripts/nostalrius/127.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33028, y = 32588, z = 13},2570) and Game.isItemThere ({x = 33006, y = 32563, z = 13},2570) and Game.isItemThere ({x = 33027, y = 32530, z = 13},2570) and Game.isItemThere ({x = 33036, y = 32507, z = 13},2570) and Game.isItemThere ({x = 33055, y = 32487, z = 13},2570) and Game.isItemThere ({x = 33077, y = 32507, z = 13},2570) and Game.isItemThere ({x = 33089, y = 32514, z = 13},2570) and Game.isItemThere ({x = 33104, y = 32514, z = 13},2570) and Game.isItemThere ({x = 33130, y = 32489, z = 13},2570) and Game.isItemThere ({x = 33147, y = 32524, z = 13},2570) and Game.isItemThere ({x = 33123, y = 32599, z = 13}, 2570) then + doRelocate(item:getPosition(),{x = 33083, y = 32571, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32571, z = 14}, 11) + else + doRelocate(item:getPosition(),{x = 33083, y = 32568, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32568, z = 13}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33083, y = 32568, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32568, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/128.lua b/data/movements/scripts/nostalrius/128.lua new file mode 100644 index 0000000..2e6cf28 --- /dev/null +++ b/data/movements/scripts/nostalrius/128.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33080, y = 32569, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33080, y = 32569, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33080, y = 32569, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33080, y = 32569, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/129.lua b/data/movements/scripts/nostalrius/129.lua new file mode 100644 index 0000000..3d3ab1d --- /dev/null +++ b/data/movements/scripts/nostalrius/129.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3235) >= 1 and creature:getPlayer():getStorageValue(266) == 0 then + doRelocate(item:getPosition(),{x = 33051, y = 32777, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33051, y = 32777, z = 14}, 11) + creature:getPlayer():removeItem(3235, 1) + else + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/13.lua b/data/movements/scripts/nostalrius/13.lua new file mode 100644 index 0000000..84929f6 --- /dev/null +++ b/data/movements/scripts/nostalrius/13.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32266, y = 31899, z = 12},{x = 32266, y = 31911, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/130.lua b/data/movements/scripts/nostalrius/130.lua new file mode 100644 index 0000000..6bcaeb6 --- /dev/null +++ b/data/movements/scripts/nostalrius/130.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:getPlayer():getStorageValue(260) == 1 then + doRelocate(item:getPosition(),{x = 33095, y = 32590, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33095, y = 32590, z = 15}, 11) + creature:getPlayer():setStorageValue(260, 0) + else + doRelocate(item:getPosition(),{x = 33073, y = 32604, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33073, y = 32604, z = 15}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33073, y = 32604, z = 15}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33073, y = 32604, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/131.lua b/data/movements/scripts/nostalrius/131.lua new file mode 100644 index 0000000..2e8744d --- /dev/null +++ b/data/movements/scripts/nostalrius/131.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33083, y = 32610, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32610, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33083, y = 32610, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33083, y = 32610, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/132.lua b/data/movements/scripts/nostalrius/132.lua new file mode 100644 index 0000000..13d22b4 --- /dev/null +++ b/data/movements/scripts/nostalrius/132.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 15) +end diff --git a/data/movements/scripts/nostalrius/133.lua b/data/movements/scripts/nostalrius/133.lua new file mode 100644 index 0000000..7dde55c --- /dev/null +++ b/data/movements/scripts/nostalrius/133.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32744, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32744, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33280, y = 32744, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32744, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/134.lua b/data/movements/scripts/nostalrius/134.lua new file mode 100644 index 0000000..dff7392 --- /dev/null +++ b/data/movements/scripts/nostalrius/134.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(276,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/135.lua b/data/movements/scripts/nostalrius/135.lua new file mode 100644 index 0000000..6bb7aa8 --- /dev/null +++ b/data/movements/scripts/nostalrius/135.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(275,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/136.lua b/data/movements/scripts/nostalrius/136.lua new file mode 100644 index 0000000..e3998c1 --- /dev/null +++ b/data/movements/scripts/nostalrius/136.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33206, y = 32577, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32577, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33206, y = 32577, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33206, y = 32577, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/137.lua b/data/movements/scripts/nostalrius/137.lua new file mode 100644 index 0000000..44b1db4 --- /dev/null +++ b/data/movements/scripts/nostalrius/137.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(272,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/138.lua b/data/movements/scripts/nostalrius/138.lua new file mode 100644 index 0000000..e4548cd --- /dev/null +++ b/data/movements/scripts/nostalrius/138.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(277,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/139.lua b/data/movements/scripts/nostalrius/139.lua new file mode 100644 index 0000000..cd7efe7 --- /dev/null +++ b/data/movements/scripts/nostalrius/139.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(274,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/14.lua b/data/movements/scripts/nostalrius/14.lua new file mode 100644 index 0000000..e3c26e0 --- /dev/null +++ b/data/movements/scripts/nostalrius/14.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32265, y = 31899, z = 12},{x = 32265, y = 31911, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/140.lua b/data/movements/scripts/nostalrius/140.lua new file mode 100644 index 0000000..676af8c --- /dev/null +++ b/data/movements/scripts/nostalrius/140.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(271,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/141.lua b/data/movements/scripts/nostalrius/141.lua new file mode 100644 index 0000000..4c6d59c --- /dev/null +++ b/data/movements/scripts/nostalrius/141.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33255, y = 32836, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33255, y = 32836, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/142.lua b/data/movements/scripts/nostalrius/142.lua new file mode 100644 index 0000000..04f2dba --- /dev/null +++ b/data/movements/scripts/nostalrius/142.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:getPlayer():setStorageValue(273,1) + item:getPosition():sendMagicEffect(13) + end +end diff --git a/data/movements/scripts/nostalrius/143.lua b/data/movements/scripts/nostalrius/143.lua new file mode 100644 index 0000000..73cb899 --- /dev/null +++ b/data/movements/scripts/nostalrius/143.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33235, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33235, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33235, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33235, y = 32705, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/144.lua b/data/movements/scripts/nostalrius/144.lua new file mode 100644 index 0000000..226cb69 --- /dev/null +++ b/data/movements/scripts/nostalrius/144.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33192, y = 32846, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33192, y = 32846, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33192, y = 32846, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33192, y = 32846, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/145.lua b/data/movements/scripts/nostalrius/145.lua new file mode 100644 index 0000000..265f081 --- /dev/null +++ b/data/movements/scripts/nostalrius/145.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33195, y = 32852, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33195, y = 32852, z = 04}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33195, y = 32852, z = 04}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33195, y = 32852, z = 04}, 11) +end diff --git a/data/movements/scripts/nostalrius/146.lua b/data/movements/scripts/nostalrius/146.lua new file mode 100644 index 0000000..507ea25 --- /dev/null +++ b/data/movements/scripts/nostalrius/146.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33157, y = 32834, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33157, y = 32834, z = 07}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33157, y = 32834, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33157, y = 32834, z = 07}, 11) +end diff --git a/data/movements/scripts/nostalrius/147.lua b/data/movements/scripts/nostalrius/147.lua new file mode 100644 index 0000000..194003e --- /dev/null +++ b/data/movements/scripts/nostalrius/147.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33133, y = 32642, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33133, y = 32642, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/148.lua b/data/movements/scripts/nostalrius/148.lua new file mode 100644 index 0000000..d9d3d84 --- /dev/null +++ b/data/movements/scripts/nostalrius/148.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33029, y = 32868, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33029, y = 32868, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33029, y = 32868, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33029, y = 32868, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/149.lua b/data/movements/scripts/nostalrius/149.lua new file mode 100644 index 0000000..4d09a1b --- /dev/null +++ b/data/movements/scripts/nostalrius/149.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33131, y = 32566, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33131, y = 32566, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/15.lua b/data/movements/scripts/nostalrius/15.lua new file mode 100644 index 0000000..45c508f --- /dev/null +++ b/data/movements/scripts/nostalrius/15.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32265, y = 31898, z = 12},{x = 32265, y = 31886, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/150.lua b/data/movements/scripts/nostalrius/150.lua new file mode 100644 index 0000000..0259c21 --- /dev/null +++ b/data/movements/scripts/nostalrius/150.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33212, y = 31671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33212, y = 31671, z = 13}, 11) + creature:getPlayer():setStorageValue(203,1) + end +end diff --git a/data/movements/scripts/nostalrius/151.lua b/data/movements/scripts/nostalrius/151.lua new file mode 100644 index 0000000..43813c6 --- /dev/null +++ b/data/movements/scripts/nostalrius/151.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33021, y = 32605, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33021, y = 32605, z = 07}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33021, y = 32605, z = 07}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33021, y = 32605, z = 07}, 15) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/152.lua b/data/movements/scripts/nostalrius/152.lua new file mode 100644 index 0000000..15901ef --- /dev/null +++ b/data/movements/scripts/nostalrius/152.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32840, y = 32533, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32840, y = 32533, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32840, y = 32533, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32840, y = 32533, z = 09}, 15) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/153.lua b/data/movements/scripts/nostalrius/153.lua new file mode 100644 index 0000000..6e048a3 --- /dev/null +++ b/data/movements/scripts/nostalrius/153.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(293) >= 17 then + doRelocate(item:getPosition(),{x = 32748, y = 32537, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32748, y = 32537, z = 10}, 21) + elseif creature:isPlayer() and creature:getPlayer():getStorageValue(293) < 17 then + doRelocate(item:getPosition(),{x = 32839, y = 32532, z = 09}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32839, y = 32532, z = 09}, 21) + end +end diff --git a/data/movements/scripts/nostalrius/154.lua b/data/movements/scripts/nostalrius/154.lua new file mode 100644 index 0000000..132b874 --- /dev/null +++ b/data/movements/scripts/nostalrius/154.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32839, y = 32532, z = 09}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32839, y = 32532, z = 09}, 21) + end +end diff --git a/data/movements/scripts/nostalrius/155.lua b/data/movements/scripts/nostalrius/155.lua new file mode 100644 index 0000000..c22e1b2 --- /dev/null +++ b/data/movements/scripts/nostalrius/155.lua @@ -0,0 +1,19 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32876, y = 32584, z = 10},4996) and Game.isItemThere ({x = 32823, y = 32525, z = 10},4996) and Game.isItemThere ({x = 32792, y = 32527, z = 10},4996) and Game.isItemThere ({x = 32744, y = 32586, z = 10}, 4996) then + doRelocate(item:getPosition(),{x = 32884, y = 32632, z = 11}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32884, y = 32632, z = 11}, 21) + else + doRelocate(item:getPosition(),{x = 32853, y = 32543, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32853, y = 32543, z = 10}, 21) + item:getPosition():sendMonsterSay("Spectral guardians ward you off.") + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32853, y = 32543, z = 10}) + item:getPosition():sendMagicEffect(21) + Game.sendMagicEffect({x = 32853, y = 32543, z = 10}, 21) + item:getPosition():sendMonsterSay("Spectral guardians ward you off.") +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/156.lua b/data/movements/scripts/nostalrius/156.lua new file mode 100644 index 0000000..968207d --- /dev/null +++ b/data/movements/scripts/nostalrius/156.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32874, y = 31942, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32874, y = 31942, z = 11}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/157.lua b/data/movements/scripts/nostalrius/157.lua new file mode 100644 index 0000000..987ae43 --- /dev/null +++ b/data/movements/scripts/nostalrius/157.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32874, y = 31953, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32874, y = 31953, z = 12}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/158.lua b/data/movements/scripts/nostalrius/158.lua new file mode 100644 index 0000000..af9c688 --- /dev/null +++ b/data/movements/scripts/nostalrius/158.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32584, y = 32465, z = 09},2567) and Game.isItemThere ({x = 32583, y = 32482, z = 09},2567) and Game.isItemThere ({x = 32610, y = 32523, z = 09},2567) and Game.isItemThere ({x = 32619, y = 32523, z = 09},2567) and Game.isItemThere ({x = 32647, y = 32483, z = 09},2567) and Game.isItemThere ({x = 32645, y = 32465, z = 09}, 2567) then + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32485, z = 10}) + Game.sendMagicEffect({x = 32615, y = 32485, z = 10}, 11) + else + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32615, y = 32484, z = 09},{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 11) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/159.lua b/data/movements/scripts/nostalrius/159.lua new file mode 100644 index 0000000..9e994e2 --- /dev/null +++ b/data/movements/scripts/nostalrius/159.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32615, y = 32483, z = 09}) + Game.sendMagicEffect({x = 32615, y = 32483, z = 09}, 13) + end +end diff --git a/data/movements/scripts/nostalrius/16.lua b/data/movements/scripts/nostalrius/16.lua new file mode 100644 index 0000000..085c05e --- /dev/null +++ b/data/movements/scripts/nostalrius/16.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not Game.isItemThere({x = 32259, y = 31891, z = 10}, 2129) then + doRelocate({x = 32259, y = 31891, z = 10},{x = 32259, y = 31892, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31891, z = 10}) + end +end diff --git a/data/movements/scripts/nostalrius/160.lua b/data/movements/scripts/nostalrius/160.lua new file mode 100644 index 0000000..fda5c49 --- /dev/null +++ b/data/movements/scripts/nostalrius/160.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32660, y = 32113, z = 08}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32660, y = 32113, z = 08}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/161.lua b/data/movements/scripts/nostalrius/161.lua new file mode 100644 index 0000000..863f13e --- /dev/null +++ b/data/movements/scripts/nostalrius/161.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() then + doRelocate(item:getPosition(),{x = 32644, y = 32104, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/162.lua b/data/movements/scripts/nostalrius/162.lua new file mode 100644 index 0000000..7193ea5 --- /dev/null +++ b/data/movements/scripts/nostalrius/162.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() then + doRelocate(item:getPosition(),{x = 32659, y = 32105, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/163.lua b/data/movements/scripts/nostalrius/163.lua new file mode 100644 index 0000000..7334336 --- /dev/null +++ b/data/movements/scripts/nostalrius/163.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isPaladin() then + doRelocate(item:getPosition(),{x = 32676, y = 32088, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/164.lua b/data/movements/scripts/nostalrius/164.lua new file mode 100644 index 0000000..d8c2e74 --- /dev/null +++ b/data/movements/scripts/nostalrius/164.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isKnight() then + doRelocate(item:getPosition(),{x = 32641, y = 32115, z = 09}) + else + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32641, y = 32141, z = 11}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/165.lua b/data/movements/scripts/nostalrius/165.lua new file mode 100644 index 0000000..468458e --- /dev/null +++ b/data/movements/scripts/nostalrius/165.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33324, y = 31592, z = 15}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33324, y = 31592, z = 15}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/166.lua b/data/movements/scripts/nostalrius/166.lua new file mode 100644 index 0000000..aef1dbf --- /dev/null +++ b/data/movements/scripts/nostalrius/166.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33316, y = 31591, z = 15}, 1949) then + doRelocate(item:getPosition(),{x = 33328, y = 31592, z = 14}) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33316, y = 31591, z = 15}, 1949) then + doRelocate(item:getPosition(),{x = 33328, y = 31592, z = 14}) + end +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/167.lua b/data/movements/scripts/nostalrius/167.lua new file mode 100644 index 0000000..5786947 --- /dev/null +++ b/data/movements/scripts/nostalrius/167.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33277, y = 31592, z = 11}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33277, y = 31592, z = 11}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33277, y = 31592, z = 11}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33277, y = 31592, z = 11}, 15) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/168.lua b/data/movements/scripts/nostalrius/168.lua new file mode 100644 index 0000000..4070a0b --- /dev/null +++ b/data/movements/scripts/nostalrius/168.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33279, y = 31592, z = 12}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33279, y = 31592, z = 12}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33279, y = 31592, z = 12}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 33279, y = 31592, z = 12}, 15) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/169.lua b/data/movements/scripts/nostalrius/169.lua new file mode 100644 index 0000000..5500abe --- /dev/null +++ b/data/movements/scripts/nostalrius/169.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33234, y = 31642, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33234, y = 31642, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33234, y = 31642, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33234, y = 31642, z = 14}, 11) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/17.lua b/data/movements/scripts/nostalrius/17.lua new file mode 100644 index 0000000..3b62616 --- /dev/null +++ b/data/movements/scripts/nostalrius/17.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not Game.isItemThere({x = 32259, y = 31890, z = 10}, 2129) then + doRelocate({x = 32259, y = 31890, z = 10},{x = 32259, y = 31889, z = 10}) + Game.createItem(2129, 1, {x = 32259, y = 31890, z = 10}) + end +end diff --git a/data/movements/scripts/nostalrius/170.lua b/data/movements/scripts/nostalrius/170.lua new file mode 100644 index 0000000..1ed4100 --- /dev/null +++ b/data/movements/scripts/nostalrius/170.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33070, y = 31620, z = 15}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33070, y = 31620, z = 15}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/171.lua b/data/movements/scripts/nostalrius/171.lua new file mode 100644 index 0000000..d19724b --- /dev/null +++ b/data/movements/scripts/nostalrius/171.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(323) == 1 and creature:getPlayer():isPremium() and creature:getPlayer():getItemCount(5021) >= 1 then + doRelocate(item:getPosition(),{x = 32498, y = 31621, z = 06}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32498, y = 31621, z = 06}, 11) + creature:getPlayer():removeItem(5021, 1) + end +end diff --git a/data/movements/scripts/nostalrius/172.lua b/data/movements/scripts/nostalrius/172.lua new file mode 100644 index 0000000..b9a40b2 --- /dev/null +++ b/data/movements/scripts/nostalrius/172.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(323) == 1 and creature:getPlayer():isPremium() and creature:getPlayer():getItemCount(5021) >= 1 then + doRelocate(item:getPosition(),{x = 32664, y = 32735, z = 06}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32664, y = 32735, z = 06}, 11) + creature:getPlayer():removeItem(5021, 1) + end +end diff --git a/data/movements/scripts/nostalrius/173.lua b/data/movements/scripts/nostalrius/173.lua new file mode 100644 index 0000000..e30d84b --- /dev/null +++ b/data/movements/scripts/nostalrius/173.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32274, y = 31847, z = 15},{x = 32171, y = 31855, z = 15}) + Game.sendMagicEffect({x = 32171, y = 31855, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/174.lua b/data/movements/scripts/nostalrius/174.lua new file mode 100644 index 0000000..1f0d072 --- /dev/null +++ b/data/movements/scripts/nostalrius/174.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32274, y = 31858, z = 15},{x = 32216, y = 31846, z = 15}) + Game.sendMagicEffect({x = 32216, y = 31846, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/175.lua b/data/movements/scripts/nostalrius/175.lua new file mode 100644 index 0000000..928430a --- /dev/null +++ b/data/movements/scripts/nostalrius/175.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32267, y = 31847, z = 15},{x = 32275, y = 31905, z = 13}) + Game.sendMagicEffect({x = 32275, y = 31905, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/176.lua b/data/movements/scripts/nostalrius/176.lua new file mode 100644 index 0000000..58bf9c3 --- /dev/null +++ b/data/movements/scripts/nostalrius/176.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32267, y = 31858, z = 15},{x = 32186, y = 31938, z = 14}) + Game.sendMagicEffect({x = 32186, y = 31938, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/177.lua b/data/movements/scripts/nostalrius/177.lua new file mode 100644 index 0000000..db20c48 --- /dev/null +++ b/data/movements/scripts/nostalrius/177.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32266, y = 31857, z = 12},{x = 32266, y = 31864, z = 12}) + Game.sendMagicEffect({x = 32266, y = 31864, z = 12}, 11) +end diff --git a/data/movements/scripts/nostalrius/178.lua b/data/movements/scripts/nostalrius/178.lua new file mode 100644 index 0000000..0c1b5b1 --- /dev/null +++ b/data/movements/scripts/nostalrius/178.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32266, y = 31863, z = 12},{x = 32259, y = 31892, z = 10}) + Game.sendMagicEffect({x = 32259, y = 31892, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/179.lua b/data/movements/scripts/nostalrius/179.lua new file mode 100644 index 0000000..887ab5b --- /dev/null +++ b/data/movements/scripts/nostalrius/179.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32262, y = 31889, z = 10},{x = 32259, y = 31892, z = 10}) + Game.sendMagicEffect({x = 32259, y = 31892, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/18.lua b/data/movements/scripts/nostalrius/18.lua new file mode 100644 index 0000000..276055b --- /dev/null +++ b/data/movements/scripts/nostalrius/18.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32104, y = 32082, z = 07},4615) and Game.isItemThere ({x = 32102, y = 32084, z = 07},2123) then + Game.removeItemOnMap({x = 32101, y = 32085, z = 07}, 3271) + Game.sendMagicEffect({x = 32101, y = 32085, z = 07}, 14) + Game.transformItemOnMap({x = 32100, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32101, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32084, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32100, y = 32085, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32085, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32100, y = 32086, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32101, y = 32086, z = 07}, 2123, 2125) + Game.transformItemOnMap({x = 32102, y = 32086, z = 07}, 2123, 2125) + end +end diff --git a/data/movements/scripts/nostalrius/180.lua b/data/movements/scripts/nostalrius/180.lua new file mode 100644 index 0000000..e06dca8 --- /dev/null +++ b/data/movements/scripts/nostalrius/180.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32259, y = 31858, z = 15},{x = 32312, y = 31974, z = 13}) + Game.sendMagicEffect({x = 32312, y = 31974, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/181.lua b/data/movements/scripts/nostalrius/181.lua new file mode 100644 index 0000000..90d533f --- /dev/null +++ b/data/movements/scripts/nostalrius/181.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32259, y = 31847, z = 15},{x = 32245, y = 31891, z = 14}) + Game.sendMagicEffect({x = 32245, y = 31891, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/182.lua b/data/movements/scripts/nostalrius/182.lua new file mode 100644 index 0000000..ab59b32 --- /dev/null +++ b/data/movements/scripts/nostalrius/182.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32201, y = 31845, z = 07}) + Game.sendMagicEffect({x = 32173, y = 31929, z = 07}, 11) + creature:getPlayer():setStorageValue(Obj2,205,1) +end diff --git a/data/movements/scripts/nostalrius/183.lua b/data/movements/scripts/nostalrius/183.lua new file mode 100644 index 0000000..8bf3847 --- /dev/null +++ b/data/movements/scripts/nostalrius/183.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32177, y = 31864, z = 15},{x = 32177, y = 31870, z = 15}) + Game.sendMagicEffect({x = 32177, y = 31870, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/184.lua b/data/movements/scripts/nostalrius/184.lua new file mode 100644 index 0000000..578689f --- /dev/null +++ b/data/movements/scripts/nostalrius/184.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32180, y = 31871, z = 15},3027) and Game.isItemThere ({x = 32173, y = 31871, z = 15},3026) then + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31863, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31870, z = 15}, 11) + Game.removeItemOnMap({x = 32173, y = 31871, z = 15}, 3026) + Game.removeItemOnMap({x = 32180, y = 31871, z = 15}, 3027) + Game.sendMagicEffect({x = 32173, y = 31871, z = 15}, 3) + Game.sendMagicEffect({x = 32180, y = 31871, z = 15}, 3) + else + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31870, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -100, -100) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32177, y = 31869, z = 15},{x = 32177, y = 31870, z = 15}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/185.lua b/data/movements/scripts/nostalrius/185.lua new file mode 100644 index 0000000..1c112de --- /dev/null +++ b/data/movements/scripts/nostalrius/185.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32176, y = 31864, z = 15},{x = 32176, y = 31870, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31870, z = 15}, 11) +end diff --git a/data/movements/scripts/nostalrius/186.lua b/data/movements/scripts/nostalrius/186.lua new file mode 100644 index 0000000..8fa55fa --- /dev/null +++ b/data/movements/scripts/nostalrius/186.lua @@ -0,0 +1,17 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32180, y = 31871, z = 15},3027) and Game.isItemThere ({x = 32173, y = 31871, z = 15},3026) then + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31863, z = 15}) + Game.sendMagicEffect({x = 32176, y = 31863, z = 15}, 11) + Game.removeItemOnMap({x = 32173, y = 31871, z = 15}, 3026) + Game.removeItemOnMap({x = 32180, y = 31871, z = 15}, 3027) + Game.sendMagicEffect({x = 32173, y = 31871, z = 15}, 3) + Game.sendMagicEffect({x = 32180, y = 31871, z = 15}, 3) + else + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31870, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -100, -100) + end +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32176, y = 31869, z = 15},{x = 32176, y = 31870, z = 15}) +end diff --git a/data/movements/scripts/nostalrius/187.lua b/data/movements/scripts/nostalrius/187.lua new file mode 100644 index 0000000..6856ee7 --- /dev/null +++ b/data/movements/scripts/nostalrius/187.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(10) == 0 and Game.isItemThere({x = 32309, y = 31975, z = 13},1996) and Game.isItemThere ({x = 32309, y = 31976, z = 13},1996) and Game.isItemThere ({x = 32311, y = 31975, z = 13},1996) and Game.isItemThere ({x = 32311, y = 31976, z = 13},1996) and Game.isItemThere ({x = 32313, y = 31975, z = 13},1998) and Game.isItemThere ({x = 32313, y = 31976, z = 13}, 1998) then + Game.sendMagicEffect({x = 32311, y = 31978, z = 13}, 7) + creature:getPlayer():setStorageValue(10,1) + doRelocate(item:getPosition(),{x = 32261, y = 31856, z = 15}) + Game.sendMagicEffect({x = 32261, y = 31856, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32311, y = 31977, z = 13}) + Game.sendMagicEffect({x = 32311, y = 31977, z = 13}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -250, -250) + end +end diff --git a/data/movements/scripts/nostalrius/188.lua b/data/movements/scripts/nostalrius/188.lua new file mode 100644 index 0000000..1263897 --- /dev/null +++ b/data/movements/scripts/nostalrius/188.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(5) == 0 then + Game.createMonster("ghost", {x = 32275, y = 31901, z = 13}) + Game.createMonster("ghost", {x = 32276, y = 31905, z = 13}) + creature:getPlayer():setStorageValue(5,1) + doRelocate(item:getPosition(),{x = 32266, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32266, y = 31849, z = 15}, 14) + Game.createMonster("demon skeleton", {x = 32275, y = 31903, z = 13}) + else + doRelocate(item:getPosition(),{x = 32277, y = 31903, z = 13}) + Game.sendMagicEffect({x = 32277, y = 31903, z = 13}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/data/movements/scripts/nostalrius/189.lua b/data/movements/scripts/nostalrius/189.lua new file mode 100644 index 0000000..9eed079 --- /dev/null +++ b/data/movements/scripts/nostalrius/189.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(4) == 0 and Game.isItemThere({x = 32243, y = 31892, z = 14},2886) then + Game.removeItemOnMap({x = 32243, y = 31892, z = 14}, 2886) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 14) + creature:getPlayer():setStorageValue(4,1) + doRelocate(item:getPosition(),{x = 32261, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32261, y = 31849, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32249, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32249, y = 31892, z = 14}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/data/movements/scripts/nostalrius/19.lua b/data/movements/scripts/nostalrius/19.lua new file mode 100644 index 0000000..0d7de80 --- /dev/null +++ b/data/movements/scripts/nostalrius/19.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32104, y = 32082, z = 07}, 4597) then + Game.transformItemOnMap({x = 32104, y = 32082, z = 07}, 4597, 4615) + end +end diff --git a/data/movements/scripts/nostalrius/190.lua b/data/movements/scripts/nostalrius/190.lua new file mode 100644 index 0000000..56e6924 --- /dev/null +++ b/data/movements/scripts/nostalrius/190.lua @@ -0,0 +1,27 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(7) == 0 and Game.isItemThere({x = 32214, y = 31850, z = 15}, 2113) then + doRelocate(item:getPosition(),{x = 32271, y = 31857, z = 15}) + creature:getPlayer():setStorageValue(7,1) + Game.sendMagicEffect({x = 32271, y = 31857, z = 15}, 14) + Game.sendMagicEffect({x = 32217, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31844, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31846, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31847, z = 14}, 12) + Game.sendMagicEffect({x = 32213, y = 31847, z = 14}, 12) + Game.sendMagicEffect({x = 32217, y = 31848, z = 14}, 12) + Game.sendMagicEffect({x = 32215, y = 31848, z = 14}, 12) + Game.createItem(2122, 1, {x = 32215, y = 31848, z = 15}) + Game.transformItemOnMap({x = 32214, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32215, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32216, y = 31850, z = 15}, 2113, 2114) + Game.transformItemOnMap({x = 32220, y = 31842, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31843, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31844, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31845, z = 15}, 2772, 2773) + Game.transformItemOnMap({x = 32220, y = 31846, z = 15}, 2772, 2773) + else + doRelocate(item:getPosition(),{x = 32215, y = 31848, z = 15}) + Game.sendMagicEffect({x = 32215, y = 31848, z = 15}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/data/movements/scripts/nostalrius/191.lua b/data/movements/scripts/nostalrius/191.lua new file mode 100644 index 0000000..35c299f --- /dev/null +++ b/data/movements/scripts/nostalrius/191.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) == 1 and creature:getPlayer():getStorageValue(9) == 0 then + creature:getPlayer():setStorageValue(9,1) + doRelocate(item:getPosition(),{x = 32268, y = 31856, z = 15}) + Game.sendMagicEffect({x = 32268, y = 31856, z = 15}, 14) + else + doRelocate(item:getPosition(),{x = 32191, y = 31938, z = 14}) + Game.sendMagicEffect({x = 32191, y = 31938, z = 14}, 1) + doTargetCombatHealth(0, creature, COMBAT_FIREDAMAGE, -55, -55) + end +end diff --git a/data/movements/scripts/nostalrius/192.lua b/data/movements/scripts/nostalrius/192.lua new file mode 100644 index 0000000..0884e8a --- /dev/null +++ b/data/movements/scripts/nostalrius/192.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(6) == 0 then + Game.createItem(2121, 1, {x = 32171, y = 31854, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31854, z = 15}) + creature:getPlayer():setStorageValue(6,1) + doRelocate(item:getPosition(),{x = 32272, y = 31849, z = 15}) + Game.sendMagicEffect({x = 32272, y = 31849, z = 15}, 14) + Game.createItem(2121, 1, {x = 32172, y = 31854, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32171, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32172, y = 31855, z = 15}) + Game.createItem(2121, 1, {x = 32170, y = 31856, z = 15}) + Game.createItem(2121, 1, {x = 32171, y = 31856, z = 15}) + Game.createItem(2121, 1, {x = 32172, y = 31856, z = 15}) + doTargetCombatHealth(0, creature, COMBAT_EARTHDAMAGE, -33, -33) + else + doRelocate(item:getPosition(),{x = 32171, y = 31854, z = 15}) + Game.sendMagicEffect({x = 32171, y = 31854, z = 15}, 1) + doTargetCombatHealth(0, creature, COMBAT_EARTHDAMAGE, -155, -155) + end +end diff --git a/data/movements/scripts/nostalrius/193.lua b/data/movements/scripts/nostalrius/193.lua new file mode 100644 index 0000000..303e4e5 --- /dev/null +++ b/data/movements/scripts/nostalrius/193.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32816, y = 31601, z = 09},3206) and creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32701, y = 31639, z = 06}) + Game.removeItemOnMap({x = 32816, y = 31601, z = 09}, 3206) + Game.sendMagicEffect({x = 32701, y = 31639, z = 06}, 11) + Game.sendMagicEffect({x = 32816, y = 31601, z = 09}, 14) + creature:getPlayer():setStorageValue(65,0) + creature:getPlayer():setStorageValue(66,0) + else + doRelocate(item:getPosition(),{x = 32818, y = 31599, z = 09}) + Game.sendMagicEffect({x = 32818, y = 31599, z = 09}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32818, y = 31599, z = 09}) + Game.sendMagicEffect({x = 32818, y = 31599, z = 09}, 11) +end diff --git a/data/movements/scripts/nostalrius/194.lua b/data/movements/scripts/nostalrius/194.lua new file mode 100644 index 0000000..fee1ef7 --- /dev/null +++ b/data/movements/scripts/nostalrius/194.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32794, y = 31577, z = 05}) + Game.sendMagicEffect({x = 32794, y = 31577, z = 05}, 11) +end diff --git a/data/movements/scripts/nostalrius/195.lua b/data/movements/scripts/nostalrius/195.lua new file mode 100644 index 0000000..1198776 --- /dev/null +++ b/data/movements/scripts/nostalrius/195.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32812, y = 31577, z = 05}) + Game.sendMagicEffect({x = 32812, y = 31577, z = 05}, 11) +end diff --git a/data/movements/scripts/nostalrius/196.lua b/data/movements/scripts/nostalrius/196.lua new file mode 100644 index 0000000..38176e6 --- /dev/null +++ b/data/movements/scripts/nostalrius/196.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32803, y = 31584, z = 01},2773) and Game.isItemThere ({x = 32805, y = 31584, z = 01},2773) and Game.isItemThere ({x = 32802, y = 31584, z = 01},2772) and Game.isItemThere ({x = 32804, y = 31584, z = 01}, 2772) then + doRelocate(item:getPosition(),{x = 32701, y = 31639, z = 06}) + Game.sendMagicEffect({x = 32701, y = 31639, z = 06}, 11) + else + doRelocate(item:getPosition(),{x = 32803, y = 31587, z = 01}) + Game.sendMagicEffect({x = 32803, y = 31587, z = 01}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32803, y = 31587, z = 01}) + Game.sendMagicEffect({x = 32803, y = 31587, z = 01}, 11) +end diff --git a/data/movements/scripts/nostalrius/197.lua b/data/movements/scripts/nostalrius/197.lua new file mode 100644 index 0000000..a932e1d --- /dev/null +++ b/data/movements/scripts/nostalrius/197.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32725, y = 31589, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32725, y = 31589, z = 12}) +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/198.lua b/data/movements/scripts/nostalrius/198.lua new file mode 100644 index 0000000..d7c534b --- /dev/null +++ b/data/movements/scripts/nostalrius/198.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33213, y = 32454, z = 01}) + creature:getPlayer():setTown(Town("Darashia")) + Game.sendMagicEffect({x = 33213, y = 32454, z = 01}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33212, y = 32455, z = 02}) + Game.sendMagicEffect({x = 33212, y = 32455, z = 02}, 14) +end diff --git a/data/movements/scripts/nostalrius/199.lua b/data/movements/scripts/nostalrius/199.lua new file mode 100644 index 0000000..82d0486 --- /dev/null +++ b/data/movements/scripts/nostalrius/199.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33217, y = 31814, z = 08}) + creature:getPlayer():setTown(Town("Edron")) + Game.sendMagicEffect({x = 33217, y = 31814, z = 08}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33210, y = 31806, z = 08}) + Game.sendMagicEffect({x = 33210, y = 31806, z = 08}, 14) +end diff --git a/data/movements/scripts/nostalrius/2.lua b/data/movements/scripts/nostalrius/2.lua new file mode 100644 index 0000000..ef20da0 --- /dev/null +++ b/data/movements/scripts/nostalrius/2.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + if Game.isItemThere({x = 33368, y = 31756, z = 11}, 355) then + Game.transformItemOnMap({x = 33368, y = 31756, z = 11}, 355, 386) + end + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + Game.transformItemOnMap({x = 33368, y = 31756, z = 11}, 386, 355) + end +end diff --git a/data/movements/scripts/nostalrius/20.lua b/data/movements/scripts/nostalrius/20.lua new file mode 100644 index 0000000..e141166 --- /dev/null +++ b/data/movements/scripts/nostalrius/20.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(293) == 7 then + creature:getPlayer():setStorageValue(296,1) + item:getPosition():sendMagicEffect(13) + item:getPosition():sendMonsterSay("!-! -O- I_I (/( --I Morgathla") + end +end diff --git a/data/movements/scripts/nostalrius/200.lua b/data/movements/scripts/nostalrius/200.lua new file mode 100644 index 0000000..6f57d37 --- /dev/null +++ b/data/movements/scripts/nostalrius/200.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 33194, y = 32853, z = 08}) + creature:getPlayer():setTown(Town("Ankrahmun")) + Game.sendMagicEffect({x = 33194, y = 32853, z = 08}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33195, y = 32851, z = 06}) + Game.sendMagicEffect({x = 33195, y = 32851, z = 06}, 14) +end diff --git a/data/movements/scripts/nostalrius/201.lua b/data/movements/scripts/nostalrius/201.lua new file mode 100644 index 0000000..6e7bacd --- /dev/null +++ b/data/movements/scripts/nostalrius/201.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32957, y = 32076, z = 07}) + creature:getPlayer():setTown(Town("Venore")) + Game.sendMagicEffect({x = 32957, y = 32076, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32952, y = 32035, z = 07}) + Game.sendMagicEffect({x = 32952, y = 32035, z = 07}, 14) +end diff --git a/data/movements/scripts/nostalrius/202.lua b/data/movements/scripts/nostalrius/202.lua new file mode 100644 index 0000000..e65c90c --- /dev/null +++ b/data/movements/scripts/nostalrius/202.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32649, y = 31925, z = 11}) + creature:getPlayer():setTown(Town("Kazordoon")) + Game.sendMagicEffect({x = 32649, y = 31925, z = 11}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32647, y = 31925, z = 12}) + Game.sendMagicEffect({x = 32647, y = 31925, z = 12}, 14) +end diff --git a/data/movements/scripts/nostalrius/203.lua b/data/movements/scripts/nostalrius/203.lua new file mode 100644 index 0000000..e65c90c --- /dev/null +++ b/data/movements/scripts/nostalrius/203.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32649, y = 31925, z = 11}) + creature:getPlayer():setTown(Town("Kazordoon")) + Game.sendMagicEffect({x = 32649, y = 31925, z = 11}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32647, y = 31925, z = 12}) + Game.sendMagicEffect({x = 32647, y = 31925, z = 12}, 14) +end diff --git a/data/movements/scripts/nostalrius/204.lua b/data/movements/scripts/nostalrius/204.lua new file mode 100644 index 0000000..d8a7edb --- /dev/null +++ b/data/movements/scripts/nostalrius/204.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32732, y = 31634, z = 07}) + creature:getPlayer():setTown(Town("Ab'Dendriel")) + Game.sendMagicEffect({x = 32732, y = 31634, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32607, y = 31681, z = 07}) + Game.sendMagicEffect({x = 32607, y = 31681, z = 07}, 14) +end diff --git a/data/movements/scripts/nostalrius/205.lua b/data/movements/scripts/nostalrius/205.lua new file mode 100644 index 0000000..aa1e0b2 --- /dev/null +++ b/data/movements/scripts/nostalrius/205.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32595, y = 32744, z = 06}) + creature:getPlayer():setTown(Town("Port Hope")) + Game.sendMagicEffect({x = 32595, y = 32744, z = 06}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32595, y = 32744, z = 06}) + Game.sendMagicEffect({x = 32595, y = 32744, z = 06}, 14) +end diff --git a/data/movements/scripts/nostalrius/206.lua b/data/movements/scripts/nostalrius/206.lua new file mode 100644 index 0000000..17fd93e --- /dev/null +++ b/data/movements/scripts/nostalrius/206.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32369, y = 32241, z = 07}) + creature:getPlayer():setTown(Town("Thais")) + Game.sendMagicEffect({x = 32369, y = 32241, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32369, y = 32242, z = 06}) + Game.sendMagicEffect({x = 32369, y = 32242, z = 06}, 14) +end diff --git a/data/movements/scripts/nostalrius/207.lua b/data/movements/scripts/nostalrius/207.lua new file mode 100644 index 0000000..83b17b7 --- /dev/null +++ b/data/movements/scripts/nostalrius/207.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32360, y = 31782, z = 07}) + creature:getPlayer():setTown(Town("Carlin")) + Game.sendMagicEffect({x = 32360, y = 31782, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32360, y = 31780, z = 08}) + Game.sendMagicEffect({x = 32360, y = 31780, z = 08}, 14) +end diff --git a/data/movements/scripts/nostalrius/208.lua b/data/movements/scripts/nostalrius/208.lua new file mode 100644 index 0000000..0f88e66 --- /dev/null +++ b/data/movements/scripts/nostalrius/208.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32569, y = 32110, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32569, y = 32110, z = 07}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32569, y = 32110, z = 07}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 32569, y = 32110, z = 07}, 11) +end diff --git a/data/movements/scripts/nostalrius/209.lua b/data/movements/scripts/nostalrius/209.lua new file mode 100644 index 0000000..5a8b655 --- /dev/null +++ b/data/movements/scripts/nostalrius/209.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32526, y = 32156, z = 12}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32526, y = 32156, z = 12}) +end diff --git a/data/movements/scripts/nostalrius/21.lua b/data/movements/scripts/nostalrius/21.lua new file mode 100644 index 0000000..146ff38 --- /dev/null +++ b/data/movements/scripts/nostalrius/21.lua @@ -0,0 +1,10 @@ +local condition = Condition(CONDITION_POISON) +condition:setTiming(1000) + +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + creature:addCondition(condition) + creature:getPlayer():setStorageValue(270,1) + Game.sendMagicEffect({x = 33362, y = 32811, z = 14}, 9) + end +end diff --git a/data/movements/scripts/nostalrius/210.lua b/data/movements/scripts/nostalrius/210.lua new file mode 100644 index 0000000..a846e12 --- /dev/null +++ b/data/movements/scripts/nostalrius/210.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32554, y = 32212, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32554, y = 32212, z = 11}) +end diff --git a/data/movements/scripts/nostalrius/211.lua b/data/movements/scripts/nostalrius/211.lua new file mode 100644 index 0000000..be623e2 --- /dev/null +++ b/data/movements/scripts/nostalrius/211.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32492, y = 31697, z = 07}) + Game.sendMagicEffect({x = 32492, y = 31697, z = 07}, 13) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32492, y = 31697, z = 07}) + Game.sendMagicEffect({x = 32492, y = 31697, z = 07}, 13) +end diff --git a/data/movements/scripts/nostalrius/212.lua b/data/movements/scripts/nostalrius/212.lua new file mode 100644 index 0000000..62d0d60 --- /dev/null +++ b/data/movements/scripts/nostalrius/212.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end diff --git a/data/movements/scripts/nostalrius/213.lua b/data/movements/scripts/nostalrius/213.lua new file mode 100644 index 0000000..28f610f --- /dev/null +++ b/data/movements/scripts/nostalrius/213.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32281, y = 32389, z = 10}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32281, y = 32389, z = 10}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32281, y = 32389, z = 10}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32281, y = 32389, z = 10}, 15) +end diff --git a/data/movements/scripts/nostalrius/214.lua b/data/movements/scripts/nostalrius/214.lua new file mode 100644 index 0000000..62d0d60 --- /dev/null +++ b/data/movements/scripts/nostalrius/214.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32167, y = 32438, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32167, y = 32438, z = 09}, 15) +end diff --git a/data/movements/scripts/nostalrius/215.lua b/data/movements/scripts/nostalrius/215.lua new file mode 100644 index 0000000..e398e00 --- /dev/null +++ b/data/movements/scripts/nostalrius/215.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32233, y = 32276, z = 9}, 1949) then + doRelocate(item:getPosition(),{x = 32225, y = 32275, z = 10}) + Game.sendMagicEffect({x = 32233, y = 32276, z = 09}, 15) + Game.sendMagicEffect({x = 32225, y = 32275, z = 10}, 15) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32233, y = 32276, z = 9}, 1949) then + doRelocate(item:getPosition(),{x = 32225, y = 32275, z = 10}) + Game.sendMagicEffect({x = 32233, y = 32276, z = 09}, 15) + Game.sendMagicEffect({x = 32225, y = 32275, z = 10}, 15) + end +end diff --git a/data/movements/scripts/nostalrius/216.lua b/data/movements/scripts/nostalrius/216.lua new file mode 100644 index 0000000..38aec49 --- /dev/null +++ b/data/movements/scripts/nostalrius/216.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32225, y = 32276, z = 10}, 1949) then + doRelocate(item:getPosition(),{x = 32232, y = 32276, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32232, y = 32276, z = 09}, 15) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32225, y = 32276, z = 10}, 1949) then + doRelocate(item:getPosition(),{x = 32232, y = 32276, z = 09}) + item:getPosition():sendMagicEffect(15) + Game.sendMagicEffect({x = 32232, y = 32276, z = 09}, 15) + end +end diff --git a/data/movements/scripts/nostalrius/217.lua b/data/movements/scripts/nostalrius/217.lua new file mode 100644 index 0000000..f1f5e31 --- /dev/null +++ b/data/movements/scripts/nostalrius/217.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32107, y = 31567, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32107, y = 31567, z = 09}) +end diff --git a/data/movements/scripts/nostalrius/218.lua b/data/movements/scripts/nostalrius/218.lua new file mode 100644 index 0000000..7b48d45 --- /dev/null +++ b/data/movements/scripts/nostalrius/218.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32189, y = 31625, z = 04}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32189, y = 31625, z = 04}) +end diff --git a/data/movements/scripts/nostalrius/219.lua b/data/movements/scripts/nostalrius/219.lua new file mode 100644 index 0000000..5cab3bd --- /dev/null +++ b/data/movements/scripts/nostalrius/219.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32081, y = 32172, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32081, y = 32172, z = 09}) +end diff --git a/data/movements/scripts/nostalrius/22.lua b/data/movements/scripts/nostalrius/22.lua new file mode 100644 index 0000000..72bd0ad --- /dev/null +++ b/data/movements/scripts/nostalrius/22.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33199, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33199, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33199, y = 32699, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33199, y = 32699, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/220.lua b/data/movements/scripts/nostalrius/220.lua new file mode 100644 index 0000000..818b085 --- /dev/null +++ b/data/movements/scripts/nostalrius/220.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32090, y = 32172, z = 09}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32090, y = 32172, z = 09}) +end diff --git a/data/movements/scripts/nostalrius/221.lua b/data/movements/scripts/nostalrius/221.lua new file mode 100644 index 0000000..0d49c0b --- /dev/null +++ b/data/movements/scripts/nostalrius/221.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32481, y = 31904, z = 04},{x = 32481, y = 31904, z = 05}) + Game.sendMagicEffect({x = 32481, y = 31904, z = 05}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32481, y = 31904, z = 04},{x = 32481, y = 31904, z = 05}) + Game.sendMagicEffect({x = 32481, y = 31904, z = 05}, 11) +end diff --git a/data/movements/scripts/nostalrius/222.lua b/data/movements/scripts/nostalrius/222.lua new file mode 100644 index 0000000..826c814 --- /dev/null +++ b/data/movements/scripts/nostalrius/222.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32481, y = 31905, z = 01},{x = 32480, y = 31905, z = 02}) + Game.sendMagicEffect({x = 32480, y = 31905, z = 02}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32481, y = 31905, z = 01},{x = 32480, y = 31905, z = 02}) + Game.sendMagicEffect({x = 32480, y = 31905, z = 02}, 11) +end diff --git a/data/movements/scripts/nostalrius/223.lua b/data/movements/scripts/nostalrius/223.lua new file mode 100644 index 0000000..caac536 --- /dev/null +++ b/data/movements/scripts/nostalrius/223.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32479, y = 31904, z = 02},{x = 32479, y = 31904, z = 03}) + Game.sendMagicEffect({x = 32479, y = 31904, z = 03}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32479, y = 31904, z = 02},{x = 32479, y = 31904, z = 03}) + Game.sendMagicEffect({x = 32479, y = 31904, z = 03}, 11) +end diff --git a/data/movements/scripts/nostalrius/224.lua b/data/movements/scripts/nostalrius/224.lua new file mode 100644 index 0000000..6295ccf --- /dev/null +++ b/data/movements/scripts/nostalrius/224.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32476, y = 31904, z = 05},{x = 32476, y = 31904, z = 06}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 06}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32476, y = 31904, z = 05},{x = 32476, y = 31904, z = 06}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 06}, 11) +end diff --git a/data/movements/scripts/nostalrius/225.lua b/data/movements/scripts/nostalrius/225.lua new file mode 100644 index 0000000..01105ca --- /dev/null +++ b/data/movements/scripts/nostalrius/225.lua @@ -0,0 +1,9 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate({x = 32476, y = 31904, z = 03},{x = 32476, y = 31904, z = 04}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 04}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate({x = 32476, y = 31904, z = 03},{x = 32476, y = 31904, z = 04}) + Game.sendMagicEffect({x = 32476, y = 31904, z = 04}, 11) +end diff --git a/data/movements/scripts/nostalrius/226.lua b/data/movements/scripts/nostalrius/226.lua new file mode 100644 index 0000000..03ac52a --- /dev/null +++ b/data/movements/scripts/nostalrius/226.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end diff --git a/data/movements/scripts/nostalrius/227.lua b/data/movements/scripts/nostalrius/227.lua new file mode 100644 index 0000000..668cc74 --- /dev/null +++ b/data/movements/scripts/nostalrius/227.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() then + doRelocate(item:getPosition(),{x = 32851, y = 32339, z = 06}) + else + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32836, y = 32294, z = 07}) +end diff --git a/data/movements/scripts/nostalrius/228.lua b/data/movements/scripts/nostalrius/228.lua new file mode 100644 index 0000000..4a6431b --- /dev/null +++ b/data/movements/scripts/nostalrius/228.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32776, y = 32255, z = 11}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32776, y = 32255, z = 11}) +end diff --git a/data/movements/scripts/nostalrius/229.lua b/data/movements/scripts/nostalrius/229.lua new file mode 100644 index 0000000..6ff5272 --- /dev/null +++ b/data/movements/scripts/nostalrius/229.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 32767, y = 32229, z = 07}) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32767, y = 32229, z = 07}) +end diff --git a/data/movements/scripts/nostalrius/23.lua b/data/movements/scripts/nostalrius/23.lua new file mode 100644 index 0000000..7b10866 --- /dev/null +++ b/data/movements/scripts/nostalrius/23.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33225, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33225, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33225, y = 32699, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33225, y = 32699, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/230.lua b/data/movements/scripts/nostalrius/230.lua new file mode 100644 index 0000000..3ca1ce9 --- /dev/null +++ b/data/movements/scripts/nostalrius/230.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() then + item:transform(453, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(452, 1) + item:decay() +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/231.lua b/data/movements/scripts/nostalrius/231.lua new file mode 100644 index 0000000..7564ca4 --- /dev/null +++ b/data/movements/scripts/nostalrius/231.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32918, y = 32072, z = 12}) + Game.sendMagicEffect({x = 32918, y = 32072, z = 11}, 13) + end +end diff --git a/data/movements/scripts/nostalrius/232.lua b/data/movements/scripts/nostalrius/232.lua new file mode 100644 index 0000000..95fb86b --- /dev/null +++ b/data/movements/scripts/nostalrius/232.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32078, z = 05}, 2114, 2113) +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32078, z = 05}, 2113, 2114) +end diff --git a/data/movements/scripts/nostalrius/233.lua b/data/movements/scripts/nostalrius/233.lua new file mode 100644 index 0000000..0935d16 --- /dev/null +++ b/data/movements/scripts/nostalrius/233.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32079, z = 5}, 2114, 2113) +end + +function onStepOut(creature, item, position, fromPosition) + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32915, y = 32079, z = 5}, 2113, 2114) +end diff --git a/data/movements/scripts/nostalrius/234.lua b/data/movements/scripts/nostalrius/234.lua new file mode 100644 index 0000000..e536b42 --- /dev/null +++ b/data/movements/scripts/nostalrius/234.lua @@ -0,0 +1,10 @@ +function onRemoveItem(item, tileitem, position) + doRelocate({x = 33336, y = 31954, z = 15},{x = 33060, y = 31623, z = 15}) + doRelocate({x = 33340, y = 31954, z = 15},{x = 33066, y = 31623, z = 15}) + doRelocate({x = 33340, y = 31958, z = 15},{x = 33066, y = 31627, z = 15}) + doRelocate({x = 33336, y = 31958, z = 15},{x = 33060, y = 31627, z = 15}) + Game.sendMagicEffect({x = 33060, y = 31622, z = 15}, 14) + Game.sendMagicEffect({x = 33066, y = 31622, z = 15}, 14) + Game.sendMagicEffect({x = 33066, y = 31628, z = 15}, 14) + Game.sendMagicEffect({x = 33060, y = 31628, z = 15}, 14) +end diff --git a/data/movements/scripts/nostalrius/235.lua b/data/movements/scripts/nostalrius/235.lua new file mode 100644 index 0000000..23db42b --- /dev/null +++ b/data/movements/scripts/nostalrius/235.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33289, y = 32481, z = 6}) +end + +function onAddItem(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33289, y = 32481, z = 6}) +end diff --git a/data/movements/scripts/nostalrius/236.lua b/data/movements/scripts/nostalrius/236.lua new file mode 100644 index 0000000..5246b4d --- /dev/null +++ b/data/movements/scripts/nostalrius/236.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + local player = Player(creature) + player:setStorageValue(260, 0) +end diff --git a/data/movements/scripts/nostalrius/237.lua b/data/movements/scripts/nostalrius/237.lua new file mode 100644 index 0000000..6b57483 --- /dev/null +++ b/data/movements/scripts/nostalrius/237.lua @@ -0,0 +1,4 @@ +function onStepIn(creature, item, position, fromPosition) + local player = Player(creature) + player:setStorageValue(260, 1) +end diff --git a/data/movements/scripts/nostalrius/24.lua b/data/movements/scripts/nostalrius/24.lua new file mode 100644 index 0000000..eb0eb7f --- /dev/null +++ b/data/movements/scripts/nostalrius/24.lua @@ -0,0 +1,13 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33198, y = 32876, z = 11},3222) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3223) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3224) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3225) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3226) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3227) and Game.isItemThere ({x = 33198, y = 32876, z = 11},3228) then + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3222) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3223) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3224) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3225) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3226) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3227) + Game.removeItemOnMap({x = 33198, y = 32876, z = 11}, 3228) + Game.createItem(3229, 1, {x = 33198, y = 32876, z = 11}) + Game.sendMagicEffect({x = 33198, y = 32876, z = 11}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/25.lua b/data/movements/scripts/nostalrius/25.lua new file mode 100644 index 0000000..fe26398 --- /dev/null +++ b/data/movements/scripts/nostalrius/25.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/data/movements/scripts/nostalrius/26.lua b/data/movements/scripts/nostalrius/26.lua new file mode 100644 index 0000000..82f0bdb --- /dev/null +++ b/data/movements/scripts/nostalrius/26.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32796, y = 31594, z = 05}, 1270) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32796, y = 31594, z = 05},{x = 32797, y = 31594, z = 05}) + Game.createItem(1270, 1, {x = 32796, y = 31594, z = 05}) + end +end diff --git a/data/movements/scripts/nostalrius/27.lua b/data/movements/scripts/nostalrius/27.lua new file mode 100644 index 0000000..06a7119 --- /dev/null +++ b/data/movements/scripts/nostalrius/27.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32796, y = 31576, z = 05}, 1270) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32796, y = 31576, z = 05},{x = 32797, y = 31576, z = 05}) + Game.createItem(1270, 1, {x = 32796, y = 31576, z = 05}) + end +end diff --git a/data/movements/scripts/nostalrius/28.lua b/data/movements/scripts/nostalrius/28.lua new file mode 100644 index 0000000..6967e3b --- /dev/null +++ b/data/movements/scripts/nostalrius/28.lua @@ -0,0 +1,16 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.removeItemOnMap({x = 32692, y = 32102, z = 10}, 1281) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32692, y = 32102, z = 10},{x = 32691, y = 32102, z = 10}) + Game.createItem(1281, 1, {x = 32692, y = 32102, z = 10}) + end +end diff --git a/data/movements/scripts/nostalrius/29.lua b/data/movements/scripts/nostalrius/29.lua new file mode 100644 index 0000000..59745b6 --- /dev/null +++ b/data/movements/scripts/nostalrius/29.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isSorcerer() and Game.isItemThere({x = 32679, y = 32089, z = 08}, 3059) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + doRelocate({x = 32692, y = 32102, z = 10},{x = 32691, y = 32102, z = 10}) + Game.createItem(1281, 1, {x = 32692, y = 32102, z = 10}) + end +end diff --git a/data/movements/scripts/nostalrius/3.lua b/data/movements/scripts/nostalrius/3.lua new file mode 100644 index 0000000..5e654fb --- /dev/null +++ b/data/movements/scripts/nostalrius/3.lua @@ -0,0 +1,14 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32771, y = 32297, z = 10},389) then + Game.removeItemOnMap({x = 32771, y = 32297, z = 10}, 389) + Game.transformItemOnMap({x = 32770, y = 32282, z = 10}, 371, 395) + end +end + +function onStepOut(creature, item, position, fromPosition) + if not Game.isItemThere({x = 32771, y = 32297, z = 10}, 389) and creature:isPlayer() then + doRelocate({x = 32771, y = 32297, z = 10},{x = 32771, y = 32296, z = 10}) + Game.createItem(389, 1, {x = 32771, y = 32297, z = 10}) + Game.transformItemOnMap({x = 32770, y = 32282, z = 10}, 395, 371) + end +end diff --git a/data/movements/scripts/nostalrius/30.lua b/data/movements/scripts/nostalrius/30.lua new file mode 100644 index 0000000..8673d9c --- /dev/null +++ b/data/movements/scripts/nostalrius/30.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isKnight() and Game.isItemThere({x = 32673, y = 32094, z = 08}, 3264) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/data/movements/scripts/nostalrius/31.lua b/data/movements/scripts/nostalrius/31.lua new file mode 100644 index 0000000..d7363a5 --- /dev/null +++ b/data/movements/scripts/nostalrius/31.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isPaladin() and Game.isItemThere({x = 32673, y = 32083, z = 08}, 3349) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/data/movements/scripts/nostalrius/32.lua b/data/movements/scripts/nostalrius/32.lua new file mode 100644 index 0000000..061d28b --- /dev/null +++ b/data/movements/scripts/nostalrius/32.lua @@ -0,0 +1,13 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():isDruid() and Game.isItemThere({x = 32667, y = 32089, z = 08}, 3585) then + item:transform(430, 1) + item:decay() + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + end +end diff --git a/data/movements/scripts/nostalrius/33.lua b/data/movements/scripts/nostalrius/33.lua new file mode 100644 index 0000000..e905df1 --- /dev/null +++ b/data/movements/scripts/nostalrius/33.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33190, y = 31629, z = 13}) and Game.isItemThere({x = 33210, y = 31630, z = 13},1295) then + Game.removeItemOnMap({x = 33210, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33211, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33212, y = 31630, z = 13}, 1295) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33190, y = 31629, z = 13}) then + doRelocate({x = 33210, y = 31630, z = 13},{x = 33210, y = 31631, z = 13}) + doRelocate({x = 33211, y = 31630, z = 13},{x = 33211, y = 31631, z = 13}) + doRelocate({x = 33212, y = 31630, z = 13},{x = 33212, y = 31631, z = 13}) + Game.createItem(1295, 1, {x = 33210, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33211, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33212, y = 31630, z = 13}) + end +end diff --git a/data/movements/scripts/nostalrius/34.lua b/data/movements/scripts/nostalrius/34.lua new file mode 100644 index 0000000..1170e40 --- /dev/null +++ b/data/movements/scripts/nostalrius/34.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33191, y = 31629, z = 13}) and Game.isItemThere({x = 33210, y = 31630, z = 13},1295) then + Game.removeItemOnMap({x = 33210, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33211, y = 31630, z = 13}, 1295) + Game.removeItemOnMap({x = 33212, y = 31630, z = 13}, 1295) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isPlayerThere({x = 33191, y = 31629, z = 13}) then + doRelocate({x = 33210, y = 31630, z = 13},{x = 33210, y = 31631, z = 13}) + doRelocate({x = 33211, y = 31630, z = 13},{x = 33211, y = 31631, z = 13}) + doRelocate({x = 33212, y = 31630, z = 13},{x = 33212, y = 31631, z = 13}) + Game.createItem(1295, 1, {x = 33210, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33211, y = 31630, z = 13}) + Game.createItem(1295, 1, {x = 33212, y = 31630, z = 13}) + end +end diff --git a/data/movements/scripts/nostalrius/35.lua b/data/movements/scripts/nostalrius/35.lua new file mode 100644 index 0000000..20d9c40 --- /dev/null +++ b/data/movements/scripts/nostalrius/35.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if not Game.isItemThere({x = 32266, y = 31861, z = 11}, 2772) then + Game.transformItemOnMap({x = 32266, y = 31861, z = 11}, 2773, 2772) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end +end + +function onAddItem(item, tileitem, position) + if not Game.isItemThere({x = 32266, y = 31861, z = 11}, 2772) then + Game.transformItemOnMap({x = 32266, y = 31861, z = 11}, 2773, 2772) + Game.transformItemOnMap({x = 32266, y = 31860, z = 11}, 411, 410) + Game.createItem(2129, 1, {x = 32266, y = 31860, z = 11}) + end +end \ No newline at end of file diff --git a/data/movements/scripts/nostalrius/36.lua b/data/movements/scripts/nostalrius/36.lua new file mode 100644 index 0000000..4e9ce6f --- /dev/null +++ b/data/movements/scripts/nostalrius/36.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32255, y = 31837, z = 09},2026) then + Game.sendMagicEffect({x = 32255, y = 31837, z = 09}, 3) + Game.removeItemOnMap({x = 32255, y = 31837, z = 09}, 2026) + doRelocate({x = 32255, y = 31837, z = 10},{x = 32255, y = 31837, z = 09}) + end +end diff --git a/data/movements/scripts/nostalrius/37.lua b/data/movements/scripts/nostalrius/37.lua new file mode 100644 index 0000000..c319e79 --- /dev/null +++ b/data/movements/scripts/nostalrius/37.lua @@ -0,0 +1,7 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(3) == 0 then + Game.createMonster("Warlock", {x = 32216, y = 31841, z = 15}) + Game.createMonster("Warlock", {x = 32216, y = 31834, z = 15}) + creature:getPlayer():setStorageValue(3,1) + end +end diff --git a/data/movements/scripts/nostalrius/38.lua b/data/movements/scripts/nostalrius/38.lua new file mode 100644 index 0000000..a26d6aa --- /dev/null +++ b/data/movements/scripts/nostalrius/38.lua @@ -0,0 +1,10 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) == 0 then + creature:getPlayer():setStorageValue(8, 1) + end + Game.sendMagicEffect(item:getPosition(), 15) +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 15) +end diff --git a/data/movements/scripts/nostalrius/39.lua b/data/movements/scripts/nostalrius/39.lua new file mode 100644 index 0000000..88e6157 --- /dev/null +++ b/data/movements/scripts/nostalrius/39.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) ~= 0 then + Game.sendMagicEffect(item:getPosition(), 15) + else + Game.sendMagicEffect(item:getPosition(), 14) + end +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 14) +end diff --git a/data/movements/scripts/nostalrius/4.lua b/data/movements/scripts/nostalrius/4.lua new file mode 100644 index 0000000..9a27453 --- /dev/null +++ b/data/movements/scripts/nostalrius/4.lua @@ -0,0 +1,8 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32592, y = 31787, z = 04},3695) and creature:getPlayer():getStorageValue(45) < 1 then + Game.removeItemOnMap({x = 32592, y = 31787, z = 04}, 3695) + Game.sendMagicEffect({x = 32592, y = 31787, z = 04}, 15) + doRelocate({x = 32592, y = 31787, z = 04},{x = 32593, y = 31787, z = 04}) + Game.createItem(3696, 1, {x = 32592, y = 31787, z = 04}) + end +end diff --git a/data/movements/scripts/nostalrius/40.lua b/data/movements/scripts/nostalrius/40.lua new file mode 100644 index 0000000..29310b2 --- /dev/null +++ b/data/movements/scripts/nostalrius/40.lua @@ -0,0 +1,10 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(8) ~= 0 then + creature:getPlayer():setStorageValue(8, 0) + end + Game.sendMagicEffect(item:getPosition(), 14) +end + +function onAddItem(item, tileitem, position) + Game.sendMagicEffect(item:getPosition(), 14) +end diff --git a/data/movements/scripts/nostalrius/41.lua b/data/movements/scripts/nostalrius/41.lua new file mode 100644 index 0000000..faffa65 --- /dev/null +++ b/data/movements/scripts/nostalrius/41.lua @@ -0,0 +1,25 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 32243, y = 31892, z = 14}, 2886) and item:getFluidType() == FLUID_BLOOD then + Game.sendMagicEffect({x = 32242, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31892, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31893, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31893, z = 14}, 1) + else + doRelocate({x = 32243, y = 31892, z = 14},{x = 32244, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 3) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32243, y = 31892, z = 14}, 2886) and item:getFluidType() == FLUID_BLOOD then + Game.sendMagicEffect({x = 32242, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31891, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31892, z = 14}, 1) + Game.sendMagicEffect({x = 32242, y = 31893, z = 14}, 1) + Game.sendMagicEffect({x = 32243, y = 31893, z = 14}, 1) + else + doRelocate({x = 32243, y = 31892, z = 14},{x = 32244, y = 31892, z = 14}) + Game.sendMagicEffect({x = 32243, y = 31892, z = 14}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/42.lua b/data/movements/scripts/nostalrius/42.lua new file mode 100644 index 0000000..5a9fb8c --- /dev/null +++ b/data/movements/scripts/nostalrius/42.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(220) < 1 then + creature:getPlayer():setStorageValue(220,1) + end +end diff --git a/data/movements/scripts/nostalrius/43.lua b/data/movements/scripts/nostalrius/43.lua new file mode 100644 index 0000000..0051d09 --- /dev/null +++ b/data/movements/scripts/nostalrius/43.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(64) < 1 then + creature:getPlayer():setStorageValue(64,1) + end +end diff --git a/data/movements/scripts/nostalrius/44.lua b/data/movements/scripts/nostalrius/44.lua new file mode 100644 index 0000000..41e1954 --- /dev/null +++ b/data/movements/scripts/nostalrius/44.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32282, z = 09}, 429, 438) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + Game.transformItemOnMap({x = 32225, y = 32282, z = 09}, 438, 429) + end +end diff --git a/data/movements/scripts/nostalrius/45.lua b/data/movements/scripts/nostalrius/45.lua new file mode 100644 index 0000000..d257246 --- /dev/null +++ b/data/movements/scripts/nostalrius/45.lua @@ -0,0 +1,19 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(430, 1) + item:decay() + Game.sendMagicEffect({x = 32468, y = 32119, z = 14}, 15) + Game.sendMagicEffect({x = 32482, y = 32170, z = 14}, 15) + Game.createItem(435, 1, {x = 32482, y = 32170, z = 14}) + end +end + +function onStepOut(creature, item, position, fromPosition) + if creature:isPlayer() then + item:transform(431, 1) + item:decay() + Game.sendMagicEffect({x = 32468, y = 32119, z = 14}, 14) + Game.sendMagicEffect({x = 32482, y = 32170, z = 14}, 14) + Game.removeItemOnMap({x = 32482, y = 32170, z = 14}, 435) + end +end diff --git a/data/movements/scripts/nostalrius/46.lua b/data/movements/scripts/nostalrius/46.lua new file mode 100644 index 0000000..a96cc13 --- /dev/null +++ b/data/movements/scripts/nostalrius/46.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32479, y = 31920, z = 07},3696) and Game.isItemThere ({x = 32478, y = 31920, z = 07},3696) and Game.isItemThere ({x = 32478, y = 31902, z = 07}, 1791) then + Game.transformItemOnMap({x = 32478, y = 31902, z = 07}, 1791, 1947) + Game.sendMagicEffect({x = 32478, y = 31902, z = 07}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/47.lua b/data/movements/scripts/nostalrius/47.lua new file mode 100644 index 0000000..976ac23 --- /dev/null +++ b/data/movements/scripts/nostalrius/47.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42,1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/48.lua b/data/movements/scripts/nostalrius/48.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/data/movements/scripts/nostalrius/48.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/49.lua b/data/movements/scripts/nostalrius/49.lua new file mode 100644 index 0000000..9d696d9 --- /dev/null +++ b/data/movements/scripts/nostalrius/49.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(43) < 1 then + creature:getPlayer():setStorageValue(43,1) + Game.sendMagicEffect({x = 32479, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/5.lua b/data/movements/scripts/nostalrius/5.lua new file mode 100644 index 0000000..8eeaaad --- /dev/null +++ b/data/movements/scripts/nostalrius/5.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(48) < 1 then + creature:getPlayer():setStorageValue(48,1) + Game.sendMagicEffect({x = 32549, y = 32142, z = 07}, 15) + end +end diff --git a/data/movements/scripts/nostalrius/50.lua b/data/movements/scripts/nostalrius/50.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/data/movements/scripts/nostalrius/50.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/51.lua b/data/movements/scripts/nostalrius/51.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/data/movements/scripts/nostalrius/51.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/52.lua b/data/movements/scripts/nostalrius/52.lua new file mode 100644 index 0000000..a3a6412 --- /dev/null +++ b/data/movements/scripts/nostalrius/52.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42, 1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/53.lua b/data/movements/scripts/nostalrius/53.lua new file mode 100644 index 0000000..9d696d9 --- /dev/null +++ b/data/movements/scripts/nostalrius/53.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(43) < 1 then + creature:getPlayer():setStorageValue(43,1) + Game.sendMagicEffect({x = 32479, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/54.lua b/data/movements/scripts/nostalrius/54.lua new file mode 100644 index 0000000..27176bc --- /dev/null +++ b/data/movements/scripts/nostalrius/54.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(44) < 1 then + creature:getPlayer():setStorageValue(44,1) + Game.sendMagicEffect({x = 32480, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/55.lua b/data/movements/scripts/nostalrius/55.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/data/movements/scripts/nostalrius/55.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/56.lua b/data/movements/scripts/nostalrius/56.lua new file mode 100644 index 0000000..8826766 --- /dev/null +++ b/data/movements/scripts/nostalrius/56.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(41) < 1 then + creature:getPlayer():setStorageValue(41,1) + Game.sendMagicEffect({x = 32477, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/57.lua b/data/movements/scripts/nostalrius/57.lua new file mode 100644 index 0000000..f61f240 --- /dev/null +++ b/data/movements/scripts/nostalrius/57.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(44) < 1 then + creature:getPlayer():setStorageValue(44,1) + Game.sendMagicEffect({x = 32480, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/58.lua b/data/movements/scripts/nostalrius/58.lua new file mode 100644 index 0000000..976ac23 --- /dev/null +++ b/data/movements/scripts/nostalrius/58.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getStorageValue(42) < 1 then + creature:getPlayer():setStorageValue(42,1) + Game.sendMagicEffect({x = 32478, y = 31900, z = 01}, 7) + end +end diff --git a/data/movements/scripts/nostalrius/59.lua b/data/movements/scripts/nostalrius/59.lua new file mode 100644 index 0000000..f2e1d96 --- /dev/null +++ b/data/movements/scripts/nostalrius/59.lua @@ -0,0 +1,17 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 32476, y = 31900, z = 05},2471) and not Game.isItemThere ({x = 32478, y = 31904, z = 05}, 1948) then + Game.createItem(1948, 1, {x = 32478, y = 31904, z = 05}) + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 431, 430) + elseif Game.isItemThere({x = 32476, y = 31900, z = 05}, 2471) then + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 431, 430) + end +end + +function onRemoveItem(item, tileitem, position) + if Game.isItemThere({x = 32478, y = 31904, z = 05},1948) then + Game.removeItemOnMap({x = 32478, y = 31904, z = 05}, 1948) + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 430, 431) + else + Game.transformItemOnMap({x = 32476, y = 31900, z = 05}, 430, 431) + end +end diff --git a/data/movements/scripts/nostalrius/6.lua b/data/movements/scripts/nostalrius/6.lua new file mode 100644 index 0000000..24cfb0f --- /dev/null +++ b/data/movements/scripts/nostalrius/6.lua @@ -0,0 +1,12 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Tile({x = 32502, y = 31890, z = 07}):getThingCount() == 2 then + doTargetCombatHealth(0, creature, COMBAT_POISONDAMAGE, -200, -200) + Game.createItem(2121, 1, {x = 32497, y = 31889, z = 07}) + Game.createItem(2121, 1, {x = 32499, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32497, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32498, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32496, y = 31890, z = 07}) + Game.createItem(2121, 1, {x = 32494, y = 31888, z = 07}) + Game.createItem(2121, 1, {x = 32502, y = 31890, z = 07}) + end +end diff --git a/data/movements/scripts/nostalrius/60.lua b/data/movements/scripts/nostalrius/60.lua new file mode 100644 index 0000000..0ba18c2 --- /dev/null +++ b/data/movements/scripts/nostalrius/60.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32563, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32565, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32567, y = 31957, z = 01},3114) and Game.isItemThere ({x = 32569, y = 31957, z = 01},3114) then + doRelocate(item:getPosition(),{x = 32479, y = 31923, z = 07}) + Game.sendMagicEffect({x = 32563, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32565, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32567, y = 31957, z = 01}, 10) + Game.sendMagicEffect({x = 32569, y = 31957, z = 01}, 10) + Game.removeItemOnMap({x = 32563, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32565, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32567, y = 31957, z = 01}, 3114) + Game.removeItemOnMap({x = 32569, y = 31957, z = 01}, 3114) + Game.createItem(2121, 1, {x = 32563, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32565, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32567, y = 31957, z = 01}) + Game.createItem(2121, 1, {x = 32569, y = 31957, z = 01}) + Game.sendMagicEffect({x = 32566, y = 31957, z = 01}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/61.lua b/data/movements/scripts/nostalrius/61.lua new file mode 100644 index 0000000..c7dc270 --- /dev/null +++ b/data/movements/scripts/nostalrius/61.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate(item:getPosition(),{x = 32566, y = 31959, z = 01}) + Game.sendMagicEffect({x = 32487, y = 31928, z = 07}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/62.lua b/data/movements/scripts/nostalrius/62.lua new file mode 100644 index 0000000..a6fe84d --- /dev/null +++ b/data/movements/scripts/nostalrius/62.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getLevel() < 2 then + doRelocate(item:getPosition(),{x = item:getPosition().x - 1, y = 32176, z = 07}) + Game.sendMagicEffect({x = item:getPosition().x - 1, y = 32176, z = 07}, 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = tileitem:getPosition().x - 1, y = 32176, z = 07}) + Game.sendMagicEffect({x = tileitem:getPosition().x - 1, y = 32176, z = 07}, 13) +end diff --git a/data/movements/scripts/nostalrius/63.lua b/data/movements/scripts/nostalrius/63.lua new file mode 100644 index 0000000..0f1e9f2 --- /dev/null +++ b/data/movements/scripts/nostalrius/63.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and not creature:getPlayer():isPremium() then + doRelocate(item:getPosition(),{x = item:getPosition().x + 3, y = item:getPosition().y, z = 07}) + Game.sendMagicEffect(item:getPosition(), 13) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = tileitem:getPosition().x + 3, y = tileitem:getPosition().y, z = 07}) + Game.sendMagicEffect(tileitem:getPosition(), 13) +end diff --git a/data/movements/scripts/nostalrius/64.lua b/data/movements/scripts/nostalrius/64.lua new file mode 100644 index 0000000..c241239 --- /dev/null +++ b/data/movements/scripts/nostalrius/64.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33293, y = 32741, z = 13},3042) then + doRelocate({x = 33293, y = 32742, z = 13},{x = 33299, y = 32742, z = 13}) + Game.sendMagicEffect({x = 33293, y = 32742, z = 13}, 11) + Game.sendMagicEffect({x = 33299, y = 32742, z = 13}, 11) + Game.sendMagicEffect({x = 33293, y = 32741, z = 13}, 16) + Game.removeItemOnMap({x = 33293, y = 32741, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33293, y = 32741, z = 13}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/65.lua b/data/movements/scripts/nostalrius/65.lua new file mode 100644 index 0000000..ba7e7c5 --- /dev/null +++ b/data/movements/scripts/nostalrius/65.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33276, y = 32552, z = 14},3042) then + doRelocate({x = 33276, y = 32553, z = 14},{x = 33271, y = 32553, z = 14}) + Game.sendMagicEffect({x = 33276, y = 32553, z = 14}, 11) + Game.sendMagicEffect({x = 33271, y = 32553, z = 14}, 11) + Game.sendMagicEffect({x = 33276, y = 32552, z = 14}, 16) + Game.removeItemOnMap({x = 33276, y = 32552, z = 14}, 3042) + else + Game.sendMagicEffect({x = 33276, y = 32552, z = 14}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/66.lua b/data/movements/scripts/nostalrius/66.lua new file mode 100644 index 0000000..73f87b8 --- /dev/null +++ b/data/movements/scripts/nostalrius/66.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33240, y = 32855, z = 13},3042) then + doRelocate({x = 33240, y = 32856, z = 13},{x = 33246, y = 32850, z = 13}) + Game.sendMagicEffect({x = 33240, y = 32856, z = 13}, 11) + Game.sendMagicEffect({x = 33246, y = 32850, z = 13}, 11) + Game.sendMagicEffect({x = 33240, y = 32855, z = 13}, 16) + Game.removeItemOnMap({x = 33240, y = 32855, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33240, y = 32855, z = 13}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/67.lua b/data/movements/scripts/nostalrius/67.lua new file mode 100644 index 0000000..b1a67c2 --- /dev/null +++ b/data/movements/scripts/nostalrius/67.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33233, y = 32692, z = 13},3042) then + doRelocate({x = 33234, y = 32692, z = 13},{x = 33234, y = 32687, z = 13}) + Game.sendMagicEffect({x = 33234, y = 32692, z = 13}, 11) + Game.sendMagicEffect({x = 33234, y = 32687, z = 13}, 11) + Game.sendMagicEffect({x = 33233, y = 32692, z = 13}, 16) + Game.removeItemOnMap({x = 33233, y = 32692, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33233, y = 32692, z = 13}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/68.lua b/data/movements/scripts/nostalrius/68.lua new file mode 100644 index 0000000..59f599d --- /dev/null +++ b/data/movements/scripts/nostalrius/68.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33161, y = 32831, z = 10},3042) then + doRelocate({x = 33162, y = 32831, z = 10},{x = 33158, y = 32832, z = 10}) + Game.sendMagicEffect({x = 33162, y = 32831, z = 10}, 11) + Game.sendMagicEffect({x = 33158, y = 32832, z = 10}, 11) + Game.sendMagicEffect({x = 33161, y = 32831, z = 10}, 16) + Game.removeItemOnMap({x = 33161, y = 32831, z = 10}, 3042) + else + Game.sendMagicEffect({x = 33161, y = 32831, z = 10}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/69.lua b/data/movements/scripts/nostalrius/69.lua new file mode 100644 index 0000000..9d713ac --- /dev/null +++ b/data/movements/scripts/nostalrius/69.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33135, y = 32682, z = 12},3042) then + doRelocate({x = 33135, y = 32683, z = 12},{x = 33130, y = 32683, z = 12}) + Game.sendMagicEffect({x = 33135, y = 32683, z = 12}, 11) + Game.sendMagicEffect({x = 33130, y = 32683, z = 12}, 11) + Game.sendMagicEffect({x = 33135, y = 32682, z = 12}, 16) + Game.removeItemOnMap({x = 33135, y = 32682, z = 12}, 3042) + else + Game.sendMagicEffect({x = 33135, y = 32682, z = 12}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/7.lua b/data/movements/scripts/nostalrius/7.lua new file mode 100644 index 0000000..e6abcbc --- /dev/null +++ b/data/movements/scripts/nostalrius/7.lua @@ -0,0 +1,6 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and Game.isItemThere({x = 32478, y = 31902, z = 07}, 1947) then + Game.transformItemOnMap({x = 32478, y = 31902, z = 07}, 1947, 1791) + Game.sendMagicEffect({x = 32478, y = 31902, z = 07}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/70.lua b/data/movements/scripts/nostalrius/70.lua new file mode 100644 index 0000000..ff4b6ec --- /dev/null +++ b/data/movements/scripts/nostalrius/70.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33098, y = 32816, z = 13},3042) then + doRelocate({x = 33097, y = 32816, z = 13},{x = 33093, y = 32824, z = 13}) + Game.sendMagicEffect({x = 33097, y = 32816, z = 13}, 11) + Game.sendMagicEffect({x = 33093, y = 32824, z = 13}, 11) + Game.sendMagicEffect({x = 33098, y = 32816, z = 13}, 16) + Game.removeItemOnMap({x = 33098, y = 32816, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33098, y = 32816, z = 13}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/71.lua b/data/movements/scripts/nostalrius/71.lua new file mode 100644 index 0000000..9a036e4 --- /dev/null +++ b/data/movements/scripts/nostalrius/71.lua @@ -0,0 +1,11 @@ +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33073, y = 32589, z = 13},3042) then + doRelocate({x = 33073, y = 32590, z = 13},{x = 33080, y = 32588, z = 13}) + Game.sendMagicEffect({x = 33073, y = 32590, z = 13}, 11) + Game.sendMagicEffect({x = 33080, y = 32589, z = 13}, 11) + Game.sendMagicEffect({x = 33073, y = 32589, z = 13}, 16) + Game.removeItemOnMap({x = 33073, y = 32589, z = 13}, 3042) + else + Game.sendMagicEffect({x = 33073, y = 32589, z = 13}, 3) + end +end diff --git a/data/movements/scripts/nostalrius/72.lua b/data/movements/scripts/nostalrius/72.lua new file mode 100644 index 0000000..6e707ff --- /dev/null +++ b/data/movements/scripts/nostalrius/72.lua @@ -0,0 +1,5 @@ +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32775, y = 31595, z = 07}) + Game.sendMagicEffect({x = 32775, y = 31595, z = 07}, 13) + Game.sendMagicEffect({x = 32701, y = 31637, z = 06}, 11) +end diff --git a/data/movements/scripts/nostalrius/73.lua b/data/movements/scripts/nostalrius/73.lua new file mode 100644 index 0000000..fcb0e0e --- /dev/null +++ b/data/movements/scripts/nostalrius/73.lua @@ -0,0 +1,5 @@ +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 32800, y = 31605, z = 07}) + Game.sendMagicEffect({x = 32800, y = 31605, z = 07}, 13) + Game.sendMagicEffect({x = 32701, y = 31638, z = 06}, 11) +end diff --git a/data/movements/scripts/nostalrius/74.lua b/data/movements/scripts/nostalrius/74.lua new file mode 100644 index 0000000..cf2eebf --- /dev/null +++ b/data/movements/scripts/nostalrius/74.lua @@ -0,0 +1,21 @@ +function onStepIn(creature, item, position, fromPosition) + if Game.isItemThere({x = 33368, y = 32763, z = 14},2567) and Game.isItemThere ({x = 33382, y = 32786, z = 14},2567) and Game.isItemThere ({x = 33305, y = 32734, z = 14},2567) and Game.isItemThere ({x = 33338, y = 32702, z = 14},2567) and Game.isItemThere ({x = 33320, y = 32682, z = 14},2567) and Game.isItemThere ({x = 33349, y = 32680, z = 14},2567) and Game.isItemThere ({x = 33358, y = 32701, z = 14},2567) and Game.isItemThere ({x = 33357, y = 32749, z = 14}, 2567) then + doRelocate(item:getPosition(),{x = 33367, y = 32805, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33367, y = 32805, z = 14}, 11) + else + doRelocate(item:getPosition(),{x = 33399, y = 32801, z = 14}) + Game.sendMagicEffect({x = 33399, y = 32801, z = 14}, 11) + end +end + +function onAddItem(item, tileitem, position) + if Game.isItemThere({x = 33368, y = 32763, z = 14},2567) and Game.isItemThere ({x = 33382, y = 32786, z = 14},2567) and Game.isItemThere ({x = 33305, y = 32734, z = 14},2567) and Game.isItemThere ({x = 33338, y = 32702, z = 14},2567) and Game.isItemThere ({x = 33320, y = 32682, z = 14},2567) and Game.isItemThere ({x = 33349, y = 32680, z = 14},2567) and Game.isItemThere ({x = 33358, y = 32701, z = 14},2567) and Game.isItemThere ({x = 33357, y = 32749, z = 14}, 2567) then + doRelocate(tileitem:getPosition(),{x = 33367, y = 32805, z = 14}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33367, y = 32805, z = 14}, 11) + else + doRelocate(tileitem:getPosition(),{x = 33399, y = 32801, z = 14}) + Game.sendMagicEffect({x = 33399, y = 32801, z = 14}, 11) + end +end diff --git a/data/movements/scripts/nostalrius/75.lua b/data/movements/scripts/nostalrius/75.lua new file mode 100644 index 0000000..2922049 --- /dev/null +++ b/data/movements/scripts/nostalrius/75.lua @@ -0,0 +1,18 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() and creature:getPlayer():getItemCount(3238) >= 1 and creature:getPlayer():getStorageValue(262) == 0 then + doRelocate(item:getPosition(),{x = 33349, y = 32830, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33349, y = 32830, z = 14}, 11) + player:removeItem(3238, 1) + else + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33280, y = 32740, z = 10}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/76.lua b/data/movements/scripts/nostalrius/76.lua new file mode 100644 index 0000000..4ccc018 --- /dev/null +++ b/data/movements/scripts/nostalrius/76.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33280, y = 32740, z = 10}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/77.lua b/data/movements/scripts/nostalrius/77.lua new file mode 100644 index 0000000..289286f --- /dev/null +++ b/data/movements/scripts/nostalrius/77.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33280, y = 32740, z = 10}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33280, y = 32740, z = 10}, 11) +end diff --git a/data/movements/scripts/nostalrius/78.lua b/data/movements/scripts/nostalrius/78.lua new file mode 100644 index 0000000..9309afc --- /dev/null +++ b/data/movements/scripts/nostalrius/78.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33265, y = 32678, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33265, y = 32678, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33265, y = 32678, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33265, y = 32678, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/79.lua b/data/movements/scripts/nostalrius/79.lua new file mode 100644 index 0000000..26d49ec --- /dev/null +++ b/data/movements/scripts/nostalrius/79.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33264, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32671, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33264, y = 32671, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32671, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/8.lua b/data/movements/scripts/nostalrius/8.lua new file mode 100644 index 0000000..e12486f --- /dev/null +++ b/data/movements/scripts/nostalrius/8.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32267, y = 31899, z = 12},{x = 32267, y = 31911, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/80.lua b/data/movements/scripts/nostalrius/80.lua new file mode 100644 index 0000000..b36163d --- /dev/null +++ b/data/movements/scripts/nostalrius/80.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33267, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33267, y = 32685, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33267, y = 32685, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33267, y = 32685, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/81.lua b/data/movements/scripts/nostalrius/81.lua new file mode 100644 index 0000000..fb10821 --- /dev/null +++ b/data/movements/scripts/nostalrius/81.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33264, y = 32695, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32695, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33264, y = 32695, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33264, y = 32695, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/82.lua b/data/movements/scripts/nostalrius/82.lua new file mode 100644 index 0000000..ed0b24b --- /dev/null +++ b/data/movements/scripts/nostalrius/82.lua @@ -0,0 +1,22 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:getPlayer():getStorageValue(271) > 0 and creature:getPlayer():getStorageValue(272) > 0 and creature:getPlayer():getStorageValue(273) > 0 and creature:getPlayer():getStorageValue(274) > 0 and creature:getPlayer():getStorageValue(275) > 0 and creature:getPlayer():getStorageValue(276) > 0 and creature:getPlayer():getStorageValue(277) > 0 then + doRelocate(item:getPosition(),{x = 33164, y = 32694, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33164, y = 32694, z = 14}, 11) + creature:getPlayer():setStorageValue(271,0) + creature:getPlayer():setStorageValue(272,0) + creature:getPlayer():setStorageValue(273,0) + creature:getPlayer():setStorageValue(274,0) + creature:getPlayer():setStorageValue(275,0) + creature:getPlayer():setStorageValue(276,0) + creature:getPlayer():setStorageValue(277,0) + else + doRelocate(item:getPosition(),{x = 33259, y = 32707, z = 13}) + Game.sendMagicEffect({x = 33259, y = 32707, z = 13}, 11) + end +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33259, y = 32707, z = 13}) + Game.sendMagicEffect({x = 33259, y = 32707, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/83.lua b/data/movements/scripts/nostalrius/83.lua new file mode 100644 index 0000000..9cdcff0 --- /dev/null +++ b/data/movements/scripts/nostalrius/83.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33260, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33260, y = 32700, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33260, y = 32700, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33260, y = 32700, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/84.lua b/data/movements/scripts/nostalrius/84.lua new file mode 100644 index 0000000..255c3ed --- /dev/null +++ b/data/movements/scripts/nostalrius/84.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33231, y = 32705, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33231, y = 32705, z = 08}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33231, y = 32705, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/85.lua b/data/movements/scripts/nostalrius/85.lua new file mode 100644 index 0000000..96dccb7 --- /dev/null +++ b/data/movements/scripts/nostalrius/85.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33259, y = 32681, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33259, y = 32681, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(tileitem:getPosition(),{x = 33259, y = 32681, z = 13}) + tileitem:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33259, y = 32681, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/86.lua b/data/movements/scripts/nostalrius/86.lua new file mode 100644 index 0000000..76bf750 --- /dev/null +++ b/data/movements/scripts/nostalrius/86.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33257, y = 32691, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33257, y = 32691, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33257, y = 32691, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33257, y = 32691, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/87.lua b/data/movements/scripts/nostalrius/87.lua new file mode 100644 index 0000000..47dd2a0 --- /dev/null +++ b/data/movements/scripts/nostalrius/87.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33256, y = 32673, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33256, y = 32673, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33256, y = 32673, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33256, y = 32673, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/88.lua b/data/movements/scripts/nostalrius/88.lua new file mode 100644 index 0000000..2f54934 --- /dev/null +++ b/data/movements/scripts/nostalrius/88.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32685, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32685, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32685, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/89.lua b/data/movements/scripts/nostalrius/89.lua new file mode 100644 index 0000000..eb0bdaa --- /dev/null +++ b/data/movements/scripts/nostalrius/89.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32704, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32704, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32704, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32704, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/9.lua b/data/movements/scripts/nostalrius/9.lua new file mode 100644 index 0000000..7861617 --- /dev/null +++ b/data/movements/scripts/nostalrius/9.lua @@ -0,0 +1,5 @@ +function onStepIn(creature, item, position, fromPosition) + if creature:isPlayer() then + doRelocate({x = 32267, y = 31898, z = 12},{x = 32267, y = 31886, z = 12}) + end +end diff --git a/data/movements/scripts/nostalrius/90.lua b/data/movements/scripts/nostalrius/90.lua new file mode 100644 index 0000000..0085008 --- /dev/null +++ b/data/movements/scripts/nostalrius/90.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33254, y = 32698, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32698, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33254, y = 32698, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33254, y = 32698, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/91.lua b/data/movements/scripts/nostalrius/91.lua new file mode 100644 index 0000000..8eeb2ae --- /dev/null +++ b/data/movements/scripts/nostalrius/91.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33247, y = 32679, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32679, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33247, y = 32679, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32679, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/92.lua b/data/movements/scripts/nostalrius/92.lua new file mode 100644 index 0000000..9d6c5c9 --- /dev/null +++ b/data/movements/scripts/nostalrius/92.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33249, y = 32693, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33249, y = 32693, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33249, y = 32693, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33249, y = 32693, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/93.lua b/data/movements/scripts/nostalrius/93.lua new file mode 100644 index 0000000..901b2a8 --- /dev/null +++ b/data/movements/scripts/nostalrius/93.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33247, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32671, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33247, y = 32671, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33247, y = 32671, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/94.lua b/data/movements/scripts/nostalrius/94.lua new file mode 100644 index 0000000..67ce67d --- /dev/null +++ b/data/movements/scripts/nostalrius/94.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33258, y = 32708, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33258, y = 32708, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33258, y = 32708, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33258, y = 32708, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/95.lua b/data/movements/scripts/nostalrius/95.lua new file mode 100644 index 0000000..0b06497 --- /dev/null +++ b/data/movements/scripts/nostalrius/95.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33211, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32700, z = 13}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33211, y = 32700, z = 13}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32700, z = 13}, 11) +end diff --git a/data/movements/scripts/nostalrius/96.lua b/data/movements/scripts/nostalrius/96.lua new file mode 100644 index 0000000..3bad916 --- /dev/null +++ b/data/movements/scripts/nostalrius/96.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33211, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32699, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33211, y = 32699, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33211, y = 32699, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/97.lua b/data/movements/scripts/nostalrius/97.lua new file mode 100644 index 0000000..88dba55 --- /dev/null +++ b/data/movements/scripts/nostalrius/97.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33025, y = 32872, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33025, y = 32872, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/98.lua b/data/movements/scripts/nostalrius/98.lua new file mode 100644 index 0000000..542c9ba --- /dev/null +++ b/data/movements/scripts/nostalrius/98.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33205, y = 33002, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33205, y = 33002, z = 14}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33205, y = 33002, z = 14}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33205, y = 33002, z = 14}, 11) +end diff --git a/data/movements/scripts/nostalrius/99.lua b/data/movements/scripts/nostalrius/99.lua new file mode 100644 index 0000000..634d341 --- /dev/null +++ b/data/movements/scripts/nostalrius/99.lua @@ -0,0 +1,11 @@ +function onStepIn(creature, item, position, fromPosition) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end + +function onAddItem(item, tileitem, position) + doRelocate(item:getPosition(),{x = 33159, y = 32838, z = 08}) + item:getPosition():sendMagicEffect(11) + Game.sendMagicEffect({x = 33159, y = 32838, z = 08}, 11) +end diff --git a/data/movements/scripts/nostalrius/_.lua b/data/movements/scripts/nostalrius/_.lua new file mode 100644 index 0000000..938ede0 --- /dev/null +++ b/data/movements/scripts/nostalrius/_.lua @@ -0,0 +1,15 @@ +function onStepIn(creature, item, position, fromPosition) + +end + +function onStepOut(creature, item, position, fromPosition) + +end + +function onAddItem(item, tileitem, position) + +end + +function onRemoveItem(item, tileitem, position) + +end diff --git a/data/npc/adrenius.npc b/data/npc/adrenius.npc new file mode 100644 index 0000000..970dce4 --- /dev/null +++ b/data/npc/adrenius.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# adrenius.npc: Datenbank für den Wüstenpriester Adrenius (Desert) + +Name = "Adrenius" +Outfit = (9,0-0-0-0) +Home = [32660,32112,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see, I am talking to someone else!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Leave me, I am used to it anyways..." + +"bye" -> "Good bye.", Idle +"name" -> "My name is Adrenius." +"job" -> "I'm a priest of Fafnar." +"fafnar" -> "Fafnar is the stronger one of the two suns above our world." +"thais" -> "Yyyyess. Yes, it's the capital city of Tibia I think." +"carlin" -> "Carlin? Don't you mean Thais?" +"king" -> "Who needs a king? I don't." +"weapon" -> "Who needs weapons? I never had and i never will have weapons - what for?" +"help" -> "Help? Help? Nothing more? Don't we all demand some help?" +"time" -> "Time? What is time? A word? A thing? An object?" +"sword" -> "Swords? Don't you have something else to do?" +"desert" -> "Sand, sand and again sand. Sand all over. Yes, I'd say: it's truly a desert!" +"excalibug" -> "What's that? You start annoying me." +"fight" -> "Leave me alone. I don't want to fight." +"god" -> "Fafnar is the greatest among the gods." +"way" -> "Way? Which way? I forgot where most ways go to... excuse me." +"door" -> "Who needs doors? Free your mind!" +"secret" -> "Secrets ... What do you mean?" +"treasure" -> "Treasures? What is a treasure for you?" +"book" -> "Read books, it increases your intelligence and, furthermore, it's a great source of inspiration!" +"gharonk" -> "Hmmmm... I don't know much about it." +"offer" -> "I can offer you religion and mysticism." +"library" -> "I heard of the library, but I never was very interested in it." +"netlios" -> "This fool! His book is nothing but a hoax! At least I believe that. Or did you find an answer for my questions?", Topic=1 + +Topic=1,"yes" -> Price=500, "By the way, I would like a donation for my temple. Are %P gold ok?", Topic=2 +Topic=1,"no" -> "Oh. So once again I am proved right." +Topic=1 -> "You can't even say 'yes' or 'no'. You are not worth talking to me!", Idle + +Topic=2,"yes",CountMoney>=Price -> DeleteMoney, "Thank you very much. Now, name me the first person in alphabetical order, his age, his fate, and how long he was on his journeys!", Topic=4 +Topic=2,"yes" -> "You want to fool me? May Fafnar burn your soul!", EffectMe(14), Burning(50,10), Idle +Topic=2 -> "Then I don't want to talk to you.", Idle + +Topic=4,"anaso","41","mother-bear","117" -> "Hmmm, maybe. What can you tell me about the second 'adventurer'?", Topic=5 +Topic=4 -> "No, sorry, that doesn't sound correct to me. Maybe you should reconsider your words one more time..." + +Topic=5,"elaeus","39","dragon","100" -> "Yes, that might be true. What did you find out about the third man?", Topic=6 +Topic=5 -> "No, no, no! Think about it, that simply can't be true!" + +Topic=6,"gadinius","42","fire","83" -> "Correct again! Hmmmm... I doubt you know anything about the fourth person!", Topic=7 +Topic=6 -> "Hmmmm... well, no. That is not true, it does not fit to the data provided by the books." + +Topic=7,"heso","40","troll","66" -> "Yes! Really, how did you figure that out? I bet, you don't know anything about the last adventurer!", Topic=8 +Topic=7 -> "No, sorry. Incorrect..." + +Topic=8,"hestus","38","poison","134" -> "That's right! Why didn't I see it? It's obvious, Netlios was right, and his stories are great! Wait, I'll give you something!", Data=4023, Create(2969) +Topic=8 -> "Well, and again it was shown: I am right and Netlios is wrong!" +} diff --git a/data/npc/ahmet.npc b/data/npc/ahmet.npc new file mode 100644 index 0000000..ec1d7c6 --- /dev/null +++ b/data/npc/ahmet.npc @@ -0,0 +1,139 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ahmet.npc: Datenbank für den pyramidenhändler Ahmet + +Name = "Ahmet" +Outfit = (130,38-40-39-114) +Home = [33126,32810,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell equipment of all sorts." +"name" -> "I am the mourned Ahmet." +"time" -> "It were foolish of me to tell you the time, because then you won't buy a watch." +"temple" -> "The temple is a school for us mourned mortals. The teachings of the temple help us to find our way through our mortal days." +"pharaoh" -> "Blessed be the pharaoh. He is our saviour. I hope that one day I will be chosen." +"arkhothep" -> * +"oldpharaoh" -> "The foolish old pharaoh withheld knowledge and power from his son, knowing that he would surpass him in every aspects. But his son granted him the chance to ascend." +"ashmunrah" -> * +"scarab" -> "The eternal burrowers are the keepers of all the secrets their kind has unearthed in countless aeons." +"chosen" -> "Only the most worthy and pious are chosen to join the armies of the pharaoh. In undeath they follow the path of ascension." + +"tibia" -> "The world is nothing but a sigil of death, a monument of decay. We have to attune to death to become one with the world." + +"carlin" -> "The vain cities of the Tibian continent think they are at the centre of the universe. Little do they know about the wisdom of the pharaoh." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "We rarely see a traveler of the small folk here." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are a rare sight in our lands." +"elves" -> * +"elfes" -> * +"darama" -> "Life here is harsh, but only this way can we deny the temptations that might damage our Rah and our Uthun to our traitorous flesh." +"darashia" -> "The foolishness of their ways will eventually spell their doom." +"daraman" -> "He was close to the truth, but he lacked the wisdom and vision of our pharaoh." +"ankrahmun" -> "Our city will endure the sands of the desert and the grinding teeth of time." + +"pharaoh" -> "Our pharaoh holds the key to our ascension. Praised be our pharaoh." +"mortality" -> "Mortality is our curse. Mourned shall we be." +"false", "gods" -> "The great traitors are trying to doom us." + +"ascension" -> "Godhood is at our disposal if only we throw of the shackles of mortal flesh." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is what we are." +"Akh" -> "Your cursed bodys are called the Akh. As long as we are alive the Akh makes us weak and vulnerable." + +"undead" -> "Undeath is the path to ascension which the chosen may follow." +"undeath" -> * +"Rah" -> "The Rah could be called our soul." +"uthun" -> "The Uthun is that what we learn and remember." +"mourn" -> "We are mortals and thus miserable creatures that are to be mourned." + +"arena" -> "The arena is east of here." +"palace" -> "The palace is the home of our beloved pharaoh." + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2863, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2871, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=40, Data=1, "Do you want to buy a water hose for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2863, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2871, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Amount=%1, Price=40*%1, Data=1, "Do you want to buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "I hope it will serve you well, my prized customer.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "My twelve starving children don't allow me to sell it for less, o grandmaster of haggling." +Topic=1 -> "What a pity." + +Topic=3,"yes",Count(Type)>=Amount -> "I hardly can explain to my wife why I gave you that much money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you own none." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=3 -> "Maybe next time." + +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, lightsources and watches. Of course, I sell fishing rods and six-packs of worms, too." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"light" -> "I sell torches, candlesticks, candelabra, and oil." + +} diff --git a/data/npc/albert.npc b/data/npc/albert.npc new file mode 100644 index 0000000..95ff403 --- /dev/null +++ b/data/npc/albert.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# albert.npc: Datenbank für den Heiler Albert + +Name = "Albert" +Outfit = (130,78-0-49-95) +Home = [33312,31762,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome in my humble hut, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "If you are heavily wounded or poisoned, feel free to return for a healing." + +"bye" -> "Good bye, %N!", Idle +"job" -> "I am a healer." +"name" -> "My Name is Albert Fibulanian." +"tibia" -> "Tibia is a world big enough for everyone. I wish people would realize that." +"thais" -> "The sinful city of Thais is a monument of corruption and murder. I am glad I left for Edron and thank the gods every day for this isle." +"edron" -> * +"god" -> "The gods of good take care of us." +"king" -> "The king does much to enhance the life of his people, but he could do more." +"tibianus" -> * +"army" -> "I dream of times which see no need for armies or warriors." +"banor" -> * +"ferumbras" -> "The fallen one is a perfect example where evil leads us to." +"excalibug" -> "It's only another instrument of pain and destruction." +"news" -> "I have only news about weather, taxes, and harvests. I heared nothing that might interest a traveller like you." +"daniel" -> "I healed his wounds, but nothing can heal his soul after the betrayal of some of his knightly brethren." +"kaine" -> "Another victim of his own ambitions. I mourn for his soul." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +"time" -> "Now, it is %T." +} diff --git a/data/npc/aldee.npc b/data/npc/aldee.npc new file mode 100644 index 0000000..eccfd4f --- /dev/null +++ b/data/npc/aldee.npc @@ -0,0 +1,175 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aldee.npc: Datenbank für den Händler Al Dee (Newbie) + +Name = "Al Dee" +Outfit = (128,97-77-87-115) +Home = [32063,32180,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'll be with you in a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant. What can I do for you?" +"name" -> "My name is Al Dee, but you can call me Al. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is save from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only I dared to take the risk, and a risk it was!" +"dallheim" -> "Some call him a hero." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich man!" +"thais" -> "Thais is a crowded town." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"club" -> "I don't buy this garbage!" +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"pick" -> Type=3462, Amount=1, "Picks are hard to come by. I trade them only for high quality small axes. Do you want to trade?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Splendid! Here take your pickaxe.", Delete(Type), Create(3456) +Topic=3,"yes" -> "Sorry, I am looking for a SMALL axe." +Topic=3,"no" -> "Well, then not." +Topic=3 -> * + + +} diff --git a/data/npc/aldo.npc b/data/npc/aldo.npc new file mode 100644 index 0000000..a3c150f --- /dev/null +++ b/data/npc/aldo.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aldo.npc: Datenbank für den Schuhverkäufer Aldo + +Name = "Aldo" +Outfit = (128,40-37-116-76) +Home = [32953,32110,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Just great, another ... 'customer'. Hello, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Yes, yes, I can only talk to one after the other! You will have to wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, get lost." + +"bye" -> "That's music in my ears.", Idle +"name" -> "I'm Aldo. No one calls me 'lucky Aldo' though, guess why!" +"job" -> "I am a salesman, I sell headgear ... uhm ... oh well, and shoes." +"time" -> "Is it time for lunch already? Hey, stop making fun of me!" +"king" -> "One day I will sell the king a pair of shoes made by me and will get out of that stinky hole I live in and my family will never find me. HE, HE!" +"tibianus" -> * +"army" -> "So many feet ... so many ... a nightmare!" +"ferumbras" -> "Can't be worse than my wife." +"wife" -> "Leave me alone with her while I am working at least. My only pleasure around here!" +"excalibug" -> "I have other stuff to worry about, like paying my bills." +"bill" -> "Yes, I have to pay o lot of bills, and some georges, and a john, and several steves." +"thais" -> "I will never in my life make it there." +"tibia" -> "I doubt I will ever see much of it. It's like i am cursed to haunt this site here for the rest of my life." +"carlin" -> "A city ruled by women!? Could anything be worse?" +"amazon" -> "I heard that chicks wear some revealing pieces of armor!" +"news" -> "Hey, I am a man. Look for some women to share gossip." +"rumour" -> * +"rumor" -> * +"hugo" -> "My boss, an evil slaver of good people like me." + +"offer" -> "I am damned to sell headgear, trousers, and shoes for the rest of my life." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"headgear" -> "We have leather helmets and studded helmets." +"shoes" -> " We sell leather boots and sandals." +"trouser" -> "We offer leather legs and studded legs." + +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"sandals" -> Type=3551, Amount=1, Price=2, "Do you want to buy one of my wonderful sandals for %P gold?", Topic=1 +"leather","boot" -> Type=3552, Amount=1, Price=2, "Do you want to buy one of my wonderful leather boots for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"studded","legs" -> Type=3362, Amount=1, Price=60, "Do you want to buy studded legs for %P gold?", Topic=1 + +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"sandals" -> Type=3551, Amount=%1, Price=2*%1, "Do you want to buy %A of my wonderful sandals for %P gold?", Topic=1 +%1,1<%1,"leather","boot" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to buy %A of my wonderful leather boots for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"studded","legs" -> Type=3362, Amount=%1, Price=60*%1, "Do you want to buy %A studded legs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here. I hope that's it now.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "At last, someone poorer than me." +Topic=1,"no" -> "Good decision!" +} diff --git a/data/npc/alesar.npc b/data/npc/alesar.npc new file mode 100644 index 0000000..deab198 --- /dev/null +++ b/data/npc/alesar.npc @@ -0,0 +1,194 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alesar.npc: Datenbank für den Djinnschmied Alesar (Waffen und Rüstungen, Efreet) + +Name = "Alesar" +Outfit = (80,0-0-0-0) +Home = [33048,32621,5] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "What do you want from me, %N?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "I am already talking to one of you creeps. So shut up until it is your turn, %N.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Humans..." + +"bye" -> "Finally.", Idle +"farewell" -> * +"name" -> "My name is none of your business." +"alesar" -> "That is my name. So what!" +"job" -> "What does it look like, fool? I'm a smith! But I won't sell you anything until Malor orders me otherwise." +"trade" -> "I won't sell you anything, human. Malor doesn't want me to trade with strangers." +"permission" -> * + +"daraman" -> "Don't you dare mention Daraman in my presence, human. I am through with his insidious lies and through with your accursed race!" +"haroun" -> "Haroun? What? You know ... where do you know that name from? Did he send you?", Topic=1 +Topic=1,"yes" -> "Treacherous liar! You would not be here if you had really talked to him. Get out of my sight or I will test my latest sabre on you!", Idle +Topic=1 -> " Of course not. How could you ... Well, at least you are honest, human. I appreciate that." + +"human" -> "I used to have illusions about you humans. I thought humans were good, noble creatures. ...", + "I thought djinns and humans shared a destiny, and that we could live side by side peacefully. ...", + "But now I have learnt my lesson. I have had the privilege to look deep into the human mind, much deeper than most of my brothers. ...", + "And guess what! I did not like what I see. You are nothing but a race of cruel, perfidious bloodsuckers who hide their wickedness behind a thin layer of civilisation and so-called humanity. ...", + "Your race is a blemish on the face of Tibia. The sooner it is gone the better!" +"djinn" -> "One day we will teach your race a lesson it will never forget." +"efreet" -> "The efreet are those djinn who never fell for Daraman's insidious propaganda. I wish I would have been as smart from the start. ...", + "But errors can be corrected!" +"marid" -> "Those among my brothers and sisters who still do not see the truth call themselves the Marid. I used to be one of them, but I left them when the truth dawned upon me. ...", + "Now I follow Malor, although I would never fight against my kind." +"gabel" -> "Gabel is a kind-hearted, honest djinn. I would hate to see him die just because he believes in Daraman's lies. ...", + "After all, I believed them myself. " +"king" -> "We need a strong king to unite us in our struggle against the humans." +"malor" -> "Malor is overambitious and unnecessarily cruel, but he is the only djinn who could unite our race, so I follow him. ...", + "The truth is I despise him, but that is of no importance as long as you humans will be exterminated." +"mal'ouquah" -> "I do not like this place. But then it does not really matter where I am. I have a forge and I don't see any humans. That's all I need. ...", + "Of course, now you are here. Doesn't help me to feel myself at home here." +"ashta'daramai",QuestValue(287)=0 -> "I used to live in Ashta'daramai. That was before I realised the extent of my blindness." +"ashta'daramai",QuestValue(287)>0 -> "Ashta'daramai is Gabel's fortress which lies to the north. ...", + "Of course you cannot enter it through the front door. ...", + "But from my time there, I know that there is also an unguarded back door in the north of the fortress." +"zathroth" -> "Legend has it that Zathroth was trying to make us beings of unalloyed evil, but he found us to be impure, so he abandoned us and started over. ...", + "It is not flattering to think we are nothing but examples of bad workmanship, but I see it from a different perspective: Since our god left us on our own it is up to ourselves to forge our destiny. ...", + "One day Zathroth will look at us in amazement." +"tibia" -> "One day we djinn will rid this world of evil." +"darashia" -> "I don't care about human cities. If I had my way, they would all be burnt to down today." +"edron" -> * +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> * + +"pharaoh" -> "The pharaoh in Ankrahmun is a dangerous fool. Just your typical human, in other words." +"palace" -> "So?" +"ascension" -> "What are you talking about? More human pseudo-philosophical flapdoodle?" +"rah" -> * +"uthun" -> * +"akh" -> * +"scarab" -> "I like them. They are peaceful, but if they are provoked they fight ferociously. And they are know to eat humans!" +"kha'zeel" -> "These mountains are our refuge from those pesky humans. Too bad there are always some who come up here anyway. You, for example." +"kha'labal" -> "The desert Kha'labal was once a beautiful land, but it was devastated in the course of the war. Damn humans! This is all your fault!" +"melchior" -> "I remember him. He was a greedy, double-dealing hyena. As far as I know his bleached bones are now lying somewhere in the Kha'labal." +"djema" -> "Djema? Well - I suppose she is the only human I still like. But she has been brought up by djinns. Who knows - perhaps humans can learn." +"baa'leal" -> "Baa'leal is Malor's lieutenant. Unflinchingly loyal, but not quite as clever as he thinks he is." +"bo'ques" -> "I miss Bo'ques' cooking, but not his pompous airs and graces." +"fa'hradin" -> "Fa'hradin, that old cynic is way too smart to believe in Daraman's lies. He should reconsider his loyalties." + +"wares" -> "I sell and buy weapons, armors, helmets, legs, and shields." +"offer" -> * +"goods" -> * +"smith" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"weapon" -> "At this time I'm only selling ice rapiers and serpent swords. But I would buy scimitars, giant swords, serpent swords, poison daggers, knight axes, dragon hammers and skull staffs from you." +"shield" -> "I am selling only ancient shields. But I buy tower shields, black shields, ancient shields and vampire shields." +"armor" -> "I am buying and selling dark armors. But I would also buy a knight armor from you." +"helmet" -> "I am buying and selling dark helmets. Furthermore I'm buying warrior helmets, strange helmets and mystic turbans." +"trousers" -> "At this time I'm only buying knight legs." +"legs" -> * + +"ice","rapier" -> Type=3284, Amount=1, Price=5000, "Do you want to buy an ice rapier for %P gold?", Topic=10 +"serpent","sword" -> Type=3297, Amount=1, Price=6000, "Do you want to buy a serpent sword for %P gold?", Topic=10 +"ancient","shield" -> Type=3432, Amount=1, Price=5000, "Do you want to buy an ancient shield for %P gold?", Topic=10 +"dark","armor" -> Type=3383, Amount=1, Price=1500, "Do you want to buy a dark armor for %P gold?", Topic=10 +"dark","helmet" -> Type=3384, Amount=1, Price=1000, "Do you want to buy a dark helmet for %P gold?", Topic=10 + +%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=5000*%1, "Do you want to buy %A ice rapiers for %P gold?", Topic=10 +%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=6000*%1, "Do you want to buy %A serpent swords for %P gold?", Topic=10 +%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=5000*%1, "Do you want to buy %A ancient shields for %P gold?", Topic=10 +%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=1500*%1, "Do you want to buy %A dark armors for %P gold?", Topic=10 +%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=1000*%1, "Do you want to buy %A dark helmets for %P gold?", Topic=10 + +"sell","serpent","sword" -> Type=3297, Amount=1, Price=900, "Do you want to sell a serpent sword for %P gold?", Topic=11 +"sell","dragon","hammer" -> Type=3322, Amount=1, Price=2000, "Do you want to sell a dragon hammer for %P gold?", Topic=11 +"sell","giant","sword" -> Type=3281, Amount=1, Price=17000, "Do you want to sell a giant sword for %P gold?", Topic=11 +"sell","poison","dagger" -> Type=3299, Amount=1, Price=50, "Do you want to sell a poison dagger for %P gold?", Topic=11 +"sell","scimitar" -> Type=3307, Amount=1, Price=150, "Do you want to sell a scimitar for %P gold?", Topic=11 +"sell","skull","staff" -> Type=3324, Amount=1, Price=6000, "Do you want to sell a skull staff for %P gold?", Topic=11 +"sell","knight","axe" -> Type=3318, Amount=1, Price=2000, "Do you want to sell a knight axe for %P gold?", Topic=11 + +"sell","tower","shield" -> Type=3428, Amount=1, Price=8000, "Do you want to sell a tower shield for %P gold?", Topic=11 +"sell","black","shield" -> Type=3429, Amount=1, Price=800, "Do you want to sell a black shield for %P gold?", Topic=11 +"sell","ancient","shield" -> Type=3432, Amount=1, Price=900, "Do you want to sell an ancient shield for %P gold?", Topic=11 +"sell","vampire","shield" -> Type=3434, Amount=1, Price=15000, "Do you want to sell a vampire shield for %P gold?", Topic=11 + +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=5000, "Do you want to sell a warrior helmet for %P gold?", Topic=11 +"sell","knight","armor" -> Type=3370, Amount=1, Price=5000, "Do you want to sell a knight armor for %P gold?", Topic=11 +"sell","knight","legs" -> Type=3371, Amount=1, Price=5000, "Do you want to sell a pair of knight legs for %P gold?", Topic=11 +"sell","strange","helmet" -> Type=3373, Amount=1, Price=500, "Do you want to sell a strange helmet for %P gold?", Topic=11 +"sell","dark","armor" -> Type=3383, Amount=1, Price=400, "Do you want to sell a dark armor for %P gold?", Topic=11 +"sell","dark","helmet" -> Type=3384, Amount=1, Price=250, "Do you want to sell a dark helmet for %P gold?", Topic=11 +"sell","mystic","turban" -> Type=3574, Amount=1, Price=150, "Do you want to sell a mystic turban for %P gold?", Topic=11 + +"sell",%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=900*%1, "Do you want to sell %A serpent swords for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","hammer" -> Type=3322, Amount=%1, Price=2000*%1, "Do you want to sell %A dragon hammers for %P gold?", Topic=11 +"sell",%1,1<%1,"giant","sword" -> Type=3281, Amount=%1, Price=17000*%1, "Do you want to sell %A giant swords for %P gold?", Topic=11 +"sell",%1,1<%1,"poison","dagger" -> Type=3299, Amount=%1, Price=50*%1, "Do you want to sell %A poison daggers for %P gold?", Topic=11 +"sell",%1,1<%1,"scimitar" -> Type=3307, Amount=%1, Price=150*%1, "Do you want to sell %A scimitars for %P gold?", Topic=11 +"sell",%1,1<%1,"skull","staff" -> Type=3324, Amount=%1, Price=6000*%1, "Do you want to sell %A skull staffs for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","axe" -> Type=3318, Amount=%1, Price=2000*%1, "Do you want to sell %A knight axes for %P gold?", Topic=11 + +"sell",%1,1<%1,"tower","shield" -> Type=3428, Amount=%1, Price=8000*%1, "Do you want to sell %A tower shields for %P gold?", Topic=11 +"sell",%1,1<%1,"black","shield" -> Type=3429, Amount=%1, Price=800*%1, "Do you want to sell %A black shields for %P gold?", Topic=11 +"sell",%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=900*%1, "Do you want to sell %A ancient shields for %P gold?", Topic=11 +"sell",%1,1<%1,"vampire","shield" -> Type=3434, Amount=%1, Price=15000*%1, "Do you want to sell %A vampire shields for %P gold?", Topic=11 + +"sell",%1,1<%1,"strange","helmet" -> Type=3373, Amount=%1, Price=500*%1, "Do you want to sell %A strange helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=400*%1, "Do you want to sell %A dark armors for %P gold?", Topic=11 +"sell",%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=250*%1, "Do you want to sell %A dark helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=5000*%1, "Do you want to sell %A warrior helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=5000*%1, "Do you want to sell %A knight armors for %P gold?", Topic=11 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=5000*%1, "Do you want to sell %A pairs of knight legs for %P gold?", Topic=11 +"sell",%1,1<%1,"mystic","turban" -> Type=3574, Amount=%1, Price=150*%1, "Do you want to sell %A mystic turbans for %P gold?", Topic=11 + +Topic=10,QuestValue(288)<3,! -> "No chance, human. Malor doesn't want me to trade with strangers." +Topic=10,"yes",CountMoney>=Price -> "Thank you. Here you are.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "You do not have enough gold." +Topic=10 -> "Well, obviously not." + +Topic=11,QuestValue(288)<3,! -> "No chance, human. Malor doesn't want me to trade with strangers." +Topic=11,"yes",Count(Type)>=Amount -> "Ok. Here is your gold.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You don't have one." +Topic=11,"yes",Amount>1 -> "You don't have that many." +Topic=11 -> "Well, obviously not." + +"mission",QuestValue(286)=3,QuestValue(287)=0 -> "So Baa'leal thinks you are up to do a mission for us? ...", + "I think he is getting old, entrusting human scum such as you are with an important mission like that. ...", + "Personally, I don't understand why you haven't been slaughtered right at the gates. ...", + "Anyway. Are you prepared to embark on a dangerous mission for us?", Topic=2 +"baa'leal",QuestValue(286)=3,QuestValue(287)=0 -> * + +Topic=2,"yes" -> "All right then, human. Have you ever heard of the 'Tears of Daraman'? ...", + "They are precious gemstones made of some unknown blue mineral and possess enormous magical power. ...", + "If you want to learn more about these gemstones don't forget to visit our library. ...", + "Anyway, one of them is enough to create thousands of our mighty djinn blades. ...", + "Unfortunately my last gemstone broke and therefore I'm not able to create new blades anymore. ...", + "To my knowledge there is only one place where you can find these gemstones - I know for a fact that the Marid have at least one of them. ...", + "Well... to cut a long story short, your mission is to sneak into Ashta'daramai and to steal it. ...", + "Needless to say, the Marid won't be too eager to part with it. Try not to get killed until you have delivered the stone to me.", SetQuestValue(287,1) +Topic=2 -> "Then not." + +"mission",QuestValue(287)>0,QuestValue(287)<3 -> "Did you find the tear of Daraman?", Topic=3 +"gem",QuestValue(287)>0,QuestValue(287)<3 -> * +"tear",QuestValue(287)>0,QuestValue(287)<3 -> * + +Topic=3,"yes",QuestValue(287)=2,Count(3233)>0 -> "So you have made it? You have really managed to steal a Tear of Daraman? ...", + "Amazing how you humans are just impossible to get rid of. Incidentally, you have this character trait in common with many insects and with other vermin. ...", + "Nevermind. I hate to say it, but it you have done us a favour, human. That gemstone will serve us well. ...", + "Baa'leal, wants you to talk to Malor concerning some new mission. ...", + "Looks like you have managed to extended your life expectancy - for just a bit longer.", Amount=1, Delete(3233), SetQuestValue(287,3) +Topic=3 -> "As I expected. You haven't got the stone. Shall I explain your mission again?", Topic=2 + +"mission",QuestValue(287)=3 -> "Don't forget to talk to Malor concerning your next mission." +"work",QuestValue(287)=3 -> * +} diff --git a/data/npc/alexander.npc b/data/npc/alexander.npc new file mode 100644 index 0000000..5d7f20e --- /dev/null +++ b/data/npc/alexander.npc @@ -0,0 +1,69 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alexander.npc: Datenbank fuer den Magiehaendler Alexander + +Name = "Alexander" +Outfit = (130,96-63-71-97) +Home = [33256,31839,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you." + +"bye" -> "See you.", Idle +"name" -> "I am Alexander." +"job" -> "I trade with runes and other magic items." +"time" -> "It's %T right now." +"king" -> "The king has not much interest in magic items as far as I know." +"tibianus" -> * +"army" -> "The army uses weapons and armor rather then items of magic." +"ferumbras" -> "A hero has to be well prepared to face this threat." +"excalibug" -> "Ah, I would trade a fortune for this fabulous item." +"thais" -> "I am glad the king founded this academy far away from the mundane troubles of Thais" +"tibia" -> "The world is filled with wonderous places and items." +"carlin" -> "I heard it's a city of druids." +"edron" -> "In our town, science and arts are thriving." +"news" -> "Ask for news and rumors in the tavern." +"rumors" -> * + +"offer" -> "I'm selling runes, life rings, wands, rods and crystal balls." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." + +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +"life","ring" -> Type=3052, Amount=1, Price=900, "Do you want to buy a life ring for %P gold?", Topic=1 +"crystal","ball" -> Type=3076, Amount=1, Price=530, "Do you want to buy a crystal ball for %P gold?", Topic=1 + +%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=900*%1, "Do you want to buy %A life rings for %P gold?", Topic=1 +%1,1<%1,"crystal","ball" -> Type=3076, Amount=%1, Price=530*%1, "Do you want to buy %A crystal balls for %P gold?", Topic=1 + +"sell","life","crystal" -> Type=3061, Amount=1, Price=85, "Do you want to sell a life crystal for %P gold?", Topic=2 +"sell","mind","stone" -> Type=3062, Amount=1, Price=170, "Do you want to sell a mind stone for %P gold?", Topic=2 +"sell","crystal","ball" -> Type=3076, Amount=1, Price=190, "Do you want to sell a crystal ball for %P gold?", Topic=2 + +"sell",%1,1<%1,"life","crystal" -> Type=3061, Amount=%1, Price=85*%1, "Do you want to sell %A life crystals for %P gold?", Topic=2 +"sell",%1,1<%1,"mind","stone" -> Type=3062, Amount=%1, Price=170*%1, "Do you want to sell %A mind stones for %P gold?", Topic=2 +"sell",%1,1<%1,"crystal","ball" -> Type=3076, Amount=%1, Price=190*%1, "Do you want to sell %A crystal balls 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." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +@"gen-t-runes-prem-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/data/npc/alia.npc b/data/npc/alia.npc new file mode 100644 index 0000000..97e19a6 --- /dev/null +++ b/data/npc/alia.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alia.npc: Datenbank für die Priesterin Alia + +Name = "Alia" +Outfit = (138,96-95-0-95) +Home = [32360,31785,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Welcome to the temple of Carlin." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Please return if you are heavily wounded or poisoned. I can heal you anytime." + +"bye" -> "May the gods be with you, %N!", Idle +"farewell" -> * +"job" -> "I'm a nun, serving the gods of Tibia in this temple. I also heal wounded adventurers." +"name" -> "My name is Alia." +"tibia" -> "That's where we are. The world Tibia." +"god" -> "They created Tibia and all life on it." +"ferumbras" -> "Don't mention this name here." +"excalibug" -> "Sorry, I can't help you with that." +"ghostlands" -> "Uh, don't ask. Thats a place even the brave women of carlin don't dare to explore them!!!" + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." +} diff --git a/data/npc/allen.npc b/data/npc/allen.npc new file mode 100644 index 0000000..375cc2f --- /dev/null +++ b/data/npc/allen.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# allen.npc: Möbelverkäufer Allen in Venore + +Name = "Allen" +Outfit = (128,76-43-38-76) +Home = [32991,32062,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Plank and Treasurechest Market, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Allen Richardson. I am the owner of this market." +"job" -> "I run this market and sell furniture." +"time" -> "It is %T. Too bad we run out of cuckoo clocks." +"news" -> "Sorry, no time to chat, let's trade." + +"offer" -> "At this counter you can buy chairs. What do you need?" +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-chairs-s.ndb" +} diff --git a/data/npc/alwin.npc b/data/npc/alwin.npc new file mode 100644 index 0000000..c8b4066 --- /dev/null +++ b/data/npc/alwin.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# alwin.npc: Datenbank für eine Stadtwache in Venore + +Name = "Alwin" +Outfit = (131,113-113-113-115) +Home = [32875,32125,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/amanda.npc b/data/npc/amanda.npc new file mode 100644 index 0000000..c682288 --- /dev/null +++ b/data/npc/amanda.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# amanda.npc: Datenbank für die Nonne Amanda + +Name = "Amanda" +Outfit = (138,96-95-0-95) +Home = [33222,31814,8] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the temple of Banor's blood %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "If you are heavily wounded or poisoned, feel free to return for a healing." + +"bye" -> "Farewell, %N!", Idle +"job" -> "I am a humble nun." +"name" -> "I am sister Amanda." +"tibia" -> "That's our world." +"god" -> "They created the world and all life on it." +"king" -> "Our king is a religious man. A shining example." +"tibianus" -> * +"army" -> "Our army lives to the ideals of Banor." +"banor" -> * +"ferumbras" -> "He is a pawn of evil." +"excalibug" -> "Only a being loyal to Banor will wield this blade." +"news" -> "Sorry, I rarely have time to chat." +"time" -> "Now, it is %T." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +} diff --git a/data/npc/amber.npc b/data/npc/amber.npc new file mode 100644 index 0000000..35ae0d6 --- /dev/null +++ b/data/npc/amber.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# amber.npc: Datenbank für die Abenteurerin Amber + +Name = "Amber" +Outfit = (137,59-113-132-76) +Home = [32103,32182,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh hello, nice to see you %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I'm already talking to someone." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"farewell" -> * +"how","are","you" -> "I am recovering from a sea journey." +"job" -> "I explore and seek adventure." +"explore" -> "I have been almost everywhere in Tibia." +"adventure" -> "I fought fierce monsters, climbed the highest mountains, and crossed the sea on a raft." +"sea" -> "My trip over the sea was horrible. The weather was bad, the waves high and my raft quite simple." +"time" -> "Sorry, I lost my watch in a storm." +"help" -> "I can't help you much beyond information." +"information" -> "Just ask and I'll try to answer." +"dungeon" -> "I have not had the time to explore the dungeons of this isle, but I have seen two big caves in the east, and there is a ruined tower in the northwest." +"sewer" -> "I like sewers. I made my very first battle experience in the Thais sewers. The small sewersystem of Rookgaard has some nasty rats to fight." +"assistant" -> "I have a job of great responsibility. Mostly I keep annoying persons away from my boss." +"monster" -> "Oh, I fought orcs, cyclopses, minotaurs, and even green dragons." +"cyclops" -> "Horrible monsters they are." +"minotaur" -> * +"dragon" -> * +"raft" -> "I left my raft at the south eastern shore. I forgot my private notebook on it. If you could return it to me I would be very grateful." +"quest" -> * +"mission" -> * +"seymour" -> "I think this poor guy was a bad choice as the head of the academy." +"academy" -> "A fine institution, but it needs definitely more funds from the king." +"king" -> "King Tibianus is the ruler of Thais." +"thais" -> "A fine city, but the king has some problems enforcing the law." +"cipfried" -> "A gentle person. You should visit him, if you have problems." +"dallheim" -> "An extraordinary warrior. He's the first and last line of defense of Rookgaard." +"hyacinth" -> "Hyacinth is a great healer. He lives somewhere hidden on this isle." +"willie" -> "He's funny in his own, gruffy way." +"obi" -> "He's a funny little man." +"weapon" -> "The best weapons on this isle are just toothpicks, compared with the weapons warriors of the mainland wield." +"magic" -> "You can learn spells only in the guildhalls of the mainland." +"tibia" -> "I try to explore each spot of Tibia, and one day I will succeed." +"castle" -> "If you travel to Thais, you really should visit the marvelous castle." + +"book" -> Type=2821, Amount=1, "Do you bring me my notebook?", Topic=1 +"notebook" -> * +Topic=1,"yes",Count(Type)>=Amount -> "Excellent. Here, take this short sword, that might serve you well.", Delete(Type), Create(3294) +Topic=1,"yes" -> "Hm, you don't have it." +Topic=1 -> "Too bad." + +"orcish" -> "I speak some orcish words, not much though, just 'yes' and 'no' and such basic.", Topic=2 +"language" -> * +"prisoner" -> * +"orc" -> "Not the nicest guys you can encounter. I had some clashes with them and was prisoner of the orcs for some months." +Topic=2,"yes" -> "It's 'mok' in orcish. I help you more about that if you have some food." +Topic=2,"no" -> "In orcish that's 'burp'. I help you more about that if you have some food." + +"food" -> "My favorite dish is salmon. Oh please, bring me some of it." +"salmon" -> Type=3579, Amount=1, "Yeah! If you give me some salmon I will tell you more about the orcish language.", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Thank you. Orcs call arrows 'pixo'.", Delete(Type) +Topic=3,"yes" -> "You don't have one!" +Topic=3 -> "Ok, then I don't tell you more about the orcish language." +} diff --git a/data/npc/anerui.npc b/data/npc/anerui.npc new file mode 100644 index 0000000..3060fe6 --- /dev/null +++ b/data/npc/anerui.npc @@ -0,0 +1,77 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# anerui.npc: Datenbank für die Jägerin Anerui + +Name = "Anerui" +Outfit = (63,0-0-0-0) +Home = [32669,31659,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"name" -> "I am Anerui Mourningleaf." +"job" -> "I am the mistress of the hunt. At this place you may buy the food our hunts provide." +"time" -> "Watch the sky, it will tell you." + +"carlin" -> "Carlin needs our protection and resources. Of course they will turn on us as soon as they feel strong enough." +"thais" -> "Thais is to far away to prove a threat but also is of little help if problems should occur." +"venore" -> "Venore profits greatly from the trade with Ab'Denriel. I see those traders as leeches that suck away our resources." +"roderick" -> "He is our contact person to the thaian kingdom and a necessary evil." +"olrik" -> "He would love to be an elf but still is more kind of a pale orc." + +"hunter" -> "Hunters live a life of freedom and closeness to nature, unlike a simple farmer or bugherder." +"hunt" -> "Hunting is an art, practiced too often by diletantes. Every fool with a bow or a spear considers himself a hunter." +"game" -> * +"prey" -> * +"forest" -> "The forests are the gardens of life. Nature provides enough for everyone's need, but not enough for everyone's greed." +"nature" -> "Nature is not a friend but an unforgiving teacher, and the lessons we have to learn are endless." +"teacher" -> "Most lessons nature teaches are about life and death." +"life" -> "Life and death are significant parts of the balance." +"death" -> * +"balance" -> "The balance of nature, of course. It's everywhere, so don't ask but observe and learn." +"bugherder" -> "Well, a person who herds bugs of course." +"bugs" -> "The bugs provide us with chitin for equipment, bugmilk, and bugmeat." +"bugmilk" -> "It's delicious. Brasith sells it in his store." +"bow" -> "Bow, arrow, and spear are the hunters' best friends. In the northeast of the town one of us may sell such tools." +"arrow" -> * +"spear" -> * +"elf" -> "That is the race to which I belong." +"elves" -> * +"dwarf" -> "I will never understand these little people of the mountains." +"human" -> "The humans are a loud and ugly race. They lack any grace and are more kin to the orcs then to us." +"troll" -> "I despise their presence in our town, but it may be a necessary evil." +"cenath" -> "The magic they wield is all that matters to them." +"kuridai" -> "The Kuridai are too agressive not only to people but also to the enviroment. They lack any understanding of the balance that we know as nature." +"deraisim" -> "We try to live in harmony with the forces of nature, may they be living or unliving." +"abdaisim" -> "The Abdaisim are our brothers and sisters in spirit. We stay in contact with them, exchanging news and items." +"teshial" -> "If they ever existed they are gone now." +"ferumbras" -> "The defiler. I will not talk about him." +"crunor" -> "I guess it's a human god for the human sight of nature. I have not much knowledge of this entity." + +"offer" -> "I sell meat and ham." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> * + +"meat" -> Type=3577, Amount=1, Price=4, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=6, "Do you want to buy ham for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=4*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=6*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/data/npc/aneus.npc b/data/npc/aneus.npc new file mode 100644 index 0000000..0613593 --- /dev/null +++ b/data/npc/aneus.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aneus.npc: Datenbank für den Geschichtenerzähler Aneus (Fields) + +Name = "Aneus" +Outfit = (129,0-50-58-116) +Home = [32426,31666,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings adventurer %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and take care!" + +"bye" -> "Good bye and take care of you!", Idle +"farewell" -> * +"name" -> "My name is Aneus, the storyteller." +"bruno" -> "I don't know much about him. I only know that he is selling fish in the village." +"marlene" -> "A lovely woman. But I give you a hint: Better keep away from her. *grin*" +"graubart" -> "I don't know much about him. But he sails much and has seen nearly the whole world." +"job" -> "I'm a storyteller." +"storyteller",! -> "Well, if you wish I can tell you the story about this place here. The story about the Fields of Glory!" + +"story",! -> "Ok, sit down and listen. Back in the early days, one of the ancestors of our king Tibianus III wanted to build the best city in whole of Tibia.", Topic=2 +"fields","of","glory",! -> "Ok, sit down and listen. Back in the early days, one of the ancestors of our king Tibianus III wanted to build the best city in whole of Tibia.", Topic=2 + +Topic=2,"ancestor",! -> "Please forgive me. I forgot his name. I'm not that young anymore.", Topic=2 +Topic=2,"city",! -> "The works on this new city began and the king sent his best soldiers to protect the workers from orcs and to make them work harder.", Topic=3 + +Topic=3,"soldier",! -> "It was the elite of the whole army. They were called the Red Legion (also known as the Bloody Legion).", Topic=3 +Topic=3,"orc" -> "The orcs attacked the workers from time to time and so they disturbed the works on the city.", Topic=4 +Topic=3,"work","harder",! -> "The soldiers treated them like slaves.", Topic=4 + +Topic=4,"slave",! -> "You dont know what a slave is? I really hope that you will never have to make this experience.", Topic=3 +Topic=4,"works",! -> "The development of the city was fine. Also a giant castle was build northeast of the city. ...", + "But more and more workers started to rebel because of the bad conditions.", Topic=5 + +Topic=5,"rebel",! -> "All rebels were brought to the giant castle. Guarded by the Red Legion, they had to work and live in even worse conditions. ...", + "Also some friends of the king's sister were brought there.", Topic=6 + +Topic=6,"friends",! -> "The king's sister was pretty upset about the situation there but her brother didn't want to do anything about this matter. ...", + "So she made a plan to destroy the Red Legion for their cruelty forever.", Topic=7 + +Topic=7,"cruelty",! -> "The soldiers treated the workers like slaves.", Topic=7 +Topic=7,"plan",! -> "She ordered her loyal druids and hunters to disguise themselves as orcs from the near island and to attack the Red Legion by night over and over again.", Topic=8 + +Topic=8,"island",! -> "The General of the Red Legion became very angry about these attacks and after some months he stroke back!", Topic=9 +Topic=8,"attack",! -> * + +Topic=9,"stroke",! -> "Most of the Red Legion went to the island by night. The orcs were not prepared and the Red Legion killed hundreds of orcs with nearly no loss. ...", + "After they were satisfied they walked back to the castle.", Topic=10 + +Topic=10,"back",! -> "It is said that the orcish shamans cursed the Red Legion. ...", + "Nobody knows. But one third of the soldiers died by a disease on the way back. ...", + "And the orcs wanted to take revenge, and after some days they stroke back! ...", + "The orcs and many allied cyclopses and minotaurs from all over Tibia came to avenge their friends, and they killed nearly all workers and soldiers in the castle. ...", + "The help of the king's sister came too late.", Topic=11 + +Topic=10,"walk",! -> "It is said that the orcish shamans cursed the Red Legion. ...", + "Nobody knows. But one third of the soldiers died by a disease on the way back. ...", + "And the orcs wanted to take revenge, and after some days they stroke back! ...", + "The orcs and many allied cyclopses and minotaurs from all over Tibia came to avenge their friends, and they killed nearly all workers and soldiers in the castle. ...", + "The help of the king's sister came too late.", Topic=11 + +Topic=11,"help",! -> "She tried to rescue the workers but it was too late. The orcs started immediately to attack her troops, too. ...", + "Her royal troops went back to the city. A trick saved the city from destruction.", Topic=12 + +Topic=12,"trick" -> "They used the same trick as against the Red Legion and the orcs started to fight their non-orcish-allies. ...", + "After a bloody long fight the orcs went back to their cities. The city of Carlin was rescued. ...", + "Since then, a woman has always been ruling over Carlin and this statue was made to remind us of their great tactics against the orcs and the Red Legion. ...", + "So that was the story of Carlin and these Fields of Glory. I hope you liked it. *smiles*" + +Topic=12,"destruction" -> "They used the same trick as against the Red Legion and the orcs started to fight their non-orcish-allies. ...", + "After a bloody long fight the orcs went back to their cities. The city of Carlin was rescued. ...", + "Since then, a woman has always been ruling over Carlin and this statue was made to remind us of their great tactics against the orcs and the Red Legion. ...", + "So that was the story of Carlin and these Fields of Glory. I hope you liked it. *smiles*" +} diff --git a/data/npc/angelina.npc b/data/npc/angelina.npc new file mode 100644 index 0000000..abb9480 --- /dev/null +++ b/data/npc/angelina.npc @@ -0,0 +1,69 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angelina.npc: Datenbank für die gefangene der dunklen mönche Angelina +Name = "Angelina" +Outfit = (136,57-79-98-95) +Home = [32635,32402,10] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "The gods must be praised that I am finally saved." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods bless you." + +"bye" -> "May the gods bless you.", Idle +"farewell" -> * +"job" -> "I am a priestess and I travelled here to learn about that order of the humble path I heard about. ...","But when I started my investigations, this false monk Lorbas thought that I was suspicious and so he ordered his minions to take me as prisoner." +"prisoner" -> "I think Lorbas liked the idea to 'convert' me to their twisted cult and saw it as a test for their leaders. Now that the magic symbols are turned off, I will gather my strength within some hours and teleport to safety." +"humble","path" -> "There are no records about the foundation of this order, and it is unknown where its 'monks' come from. Yet, travellers told us that they are living near the remains of the dark cathedral." +"monk" -> "I learnt that these monks are impostors that use false promises to lure unwary ones into the arms of their strange cult which seems to have more political than religious agendas." +"cult" -> "The cult is secretly looking for the unsatisfied, disgrunteled and poor. Its members promise such sad individuals wealth, revenge and a cause. ...", "They lure them into the cells of their cult. Here they learn how to undermine the authorities of their cities. They are trained as thieves, spies and smugglers first. ...", "Those who prove themselves as the most promising candidates are recruited to a special hidden circle. There they learn the dark arts of poisoning and murder, or elocution and agitation to become assassins and recruiters for the cult. ...","I know nothing about their agenda but I am quite sure there has to be some higher power behind all of this." +"power" -> "I have no idea who is the mastermind behind all this, but it seems too big and too well organised to be the work of only a handful of false monks." +"cathedral" -> "The cathedral was meant to be a centre of piety and believe. A prayer to the gods that had become solid. ...", + "The construction works started at the height of the Order of the Nightmare Knights, right after they had won a major battle near the place where the cathedral was to be built. ...", + "The cathedral was meant to become a monument of the victory of good over evil. ...", + "Sadly it was just not meant to be. ...", + "As the cathedral was nearly finished, most of the monks had already moved in and even a small town for all the workers and suppliers had established itself. ...", + "But then the structure was struck by an earthquake and the work of two generations was destroyed. ...", + "Later the dwarven constructors explained that this was caused by volcanic activities and a massive cave-in. ...", + "Since the gods did not interfere and the setting was close to the notorious Pits of Inferno, it was assumed that this was the work of secret demonic powers." + +"king" -> "The king is a wise ruler but his realm is large and we all need to work hard to make the world a better place." +"venore" -> "Sadly the trade barons care more about wealth than the gods." +"thais" -> "Many see Thais as a fallen city but it is only the loudness of an ugly minority that gives people this impression." +"carlin" -> "The druids have their own way to interpret the gods' will and this has to be respected." +"edron" -> "The downfall of some of the most noble knights there should serve us as a warning to stay on guard for the evil that wants to lure us on the wrong path." +"gods" -> "I would love to discuss the teachings of the gods with you but this is neither the time nor the place." + +"tibia" -> "We all have to help to make this world a better place." + +"kazordoon" -> "The dwarves carry bitterness and pain in their souls. But it is them that have forgotten about the gods and not the other way around." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The elves have lost their balance and identity. In this unstable state they can easily be misled or might draw the wrong conclusions." +"elves" -> * +"elfs" -> * +"darama" -> "A far away continent that will widen our view of the wonders the gods provide us with." +"darashia" -> "I know only little about the teachings of Daraman but as far as I heard they concentrate too much on the single individual instead on the world as a whole." +"ankrahmun" -> "This city is the best example where godless philosophies might lead to." +"ferumbras" -> "He is only one of the many servants of the evil. Eventually he will fall but there will be others to take his place." +"excalibug" -> "One day this weapon will be unearthed and then it will be wielded against the servants of the evil." +"assassin" -> "The assassins are the eyes and the long arm of this damnable cult. They eliminate the enemies and those who found out too much about their plans. Be aware of that and always watch your back." +"dark","monk" -> "The dark monks are the teachers and seducers of this cult. They work covertly in the cities and train thieves and assassins in the underground base here." +"teleport" -> "I am still gathering my strength for a teleport home, but some power already has returned. Do you wish to be teleported out of this cell?",topic=1 +"safety" -> * +"help" -> * +"escape" -> * +"out" -> * +"door" -> * +Topic=1,"yes" -> "So be it!", Idle, EffectOpp(11), Teleport(32626,32402,10), EffectOpp(11) +Topic=1 -> "As you wish." + +} + diff --git a/data/npc/angus.npc b/data/npc/angus.npc new file mode 100644 index 0000000..4c5ea7e --- /dev/null +++ b/data/npc/angus.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angus: Datenbank für den Teamassitstenten der explorers society Angus + +Name = "Angus" +Outfit = (133,57-113-95-113) +Home = [32670,32730,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, what can I do for you?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "Good bye.", Idle +"farewell" -> * + +################ Später ab hier besser bd nutzen +@"explorer.ndb" + +"mission",QuestValue(300)=12,QuestValue(320)<1 -> "With the objects you've provided our researchers will make steady progress. Still we are missing some test results from fellow explorers ...", "Please travel to our base in Northport and ask them to mail us their latest research reports. Then return here and ask about new missions.",SetQuestValue(320,1) + + +##### +"research","reports",QuestValue(320)=2 -> "Oh, yes! Tell our fellow explorer that the papers are in the mail already.",SetQuestValue(320,4) +"mission",QuestValue(320)=2 -> * + +##### +"mission",QuestValue(320)=3 -> "The reports from Northport have already arrived here and our progress is astonishing. We think it is possible to create an astral bridge between our bases. Are you interested to assist us with this?",topic=33 + +##### +"no",topic=33 -> "Perhaps you are interested some other time." +"yes",topic=33 -> "Good, just take this spectral essence and use it on the strange carving in this building as well as on the corresponding tile in our base at Northport ...", "As soon as you have charged the portal tiles that way, report about the spectral portals.", Create(4840),SetQuestValue(320,5) + +##### topic 34 verwendet + +} + + + diff --git a/data/npc/apparition.npc b/data/npc/apparition.npc new file mode 100644 index 0000000..21aab55 --- /dev/null +++ b/data/npc/apparition.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "An Apparition" +Outfit = (48,0-0-0-0) +Home = [32204,31788,5] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/arito.npc b/data/npc/arito.npc new file mode 100644 index 0000000..2721340 --- /dev/null +++ b/data/npc/arito.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# arito.npc: Datenbank fuer den Wirt Arito + +Name = "Arito" +Outfit = (132,59-74-62-115) +Home = [33069,32886,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","frodo",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"hi$","frodo",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please show some patience, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please visit us again." + +"bye" -> "Do visit us again.", Idle +"farewell" -> * +"job" -> "I am the owner of this tavern." +"tavern" -> "This tavern is called the 'Old Scarab's Shell'." + +"name" -> "My name is Arito." +"time" -> "It is exactly %T." +"pharaoh" -> "Blessed be our saviour." +"tibianus" -> "A foolish king who resides over foolish mortals." + +"army" -> "Our army is strong and unyielding." +"ferumbras" -> "This servant of evil won't even dare to enter our city and to call the wrath of our pharaoh upon him." +"arena" -> "In the arena life challenges death. Death will be victorious in the end, but in the meantime there is much for the living to learn in preparation." +"excalibug" -> "Our pharaoh does not have any use for such a weapon. Powerful though it may be, it is nothing compared to his divine power." +"thais" -> "Thais is the capital of an insolent realm. Its people embrace life without understanding the alternative." +"tibia" -> "Why, this is our world of course." +"carlin" -> "Carlin is the twin sister of Thais. Another city that has not found the true path yet." + +"news" -> "I've heard some blasphemous adventurers have excavated one of the ancient burial sites in the desert." +"rumors" -> * + +"darama" -> "This is our continent. Ankrahmun is its biggest and most marvelous city." +"darashia" -> "A city of the lost." +"daraman" -> "I know little about his heretic teachings." +"ankrahmun" -> "Our city is a marvel. It is the envy of the whole world." +"city" -> * + +"pharaoh" -> "Our pharaoh is our father, shepherd and teacher." +"arkhothep" -> * +"mortality" -> "Mortality keeps us from finding our way to ascension." + +"ascension" -> "For us mortals ascension is but a distant dream." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is what constitutes our self." +"Akh" -> "The Akh is our body, both in death and in life." + +"undead" -> "Undeath is a blessing." +"undeath" -> * +"Rah" -> "The Rah is our lifeforce. It is the source of our inner light." +"uthun" -> "The Uthun is what we learn and remember." +"mourn" -> "Mortality is a curse. That is why mortals have to be mourned." + +"arena" -> "The arena is located close to the centre of Ankrahmun." +"palace" -> "The residence of our beloved pharaoh can be found to the south of the arena." +"temple" -> "The temple is to the east, not far from the shore." + +"buy" -> "I can offer you bread, cheese, ham or meat." +"offer" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Looking for food? I have lemonade, wine, water, bread, cheese, ham, meat and fish." + +"bread" -> Type=3600, Amount=1, Price=8, "Would you like to buy bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=12, "Would you like to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=10, "Would you like to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=16, "Would you like to buy a ham for %P gold?", Topic=1 +"fish" -> Type=3578, Amount=1, Price=6, "Would you like to buy a fish for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=8*%1, "Would you like to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=12*%1, "Would you like to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=10*%1, "Would you like to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=16*%1, "Would you like to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=6*%1, "Would you like to buy %A fishes for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=3, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=4, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=2, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +} diff --git a/data/npc/arkhothep.npc b/data/npc/arkhothep.npc new file mode 100644 index 0000000..e82711a --- /dev/null +++ b/data/npc/arkhothep.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Arkhothep.npc: Datenbank für den Pharao von Ankrahmun + +Name = "Arkhothep" +Outfit = (91,0-0-0-0) +Home = [33150,32842,4] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> Idle +ADDRESS,"hi$",! -> Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/arnold.npc b/data/npc/arnold.npc new file mode 100644 index 0000000..fce03bb --- /dev/null +++ b/data/npc/arnold.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# arnold.npc: Datenbank für eine Stadtwache in Venore + +Name = "Arnold" +Outfit = (131,113-113-113-115) +Home = [32945,32070,6] +Radius = 6 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/aruda.npc b/data/npc/aruda.npc new file mode 100644 index 0000000..2706a32 --- /dev/null +++ b/data/npc/aruda.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# aruda.npc: Datenbank fuer die Diebin Aruda + +Name = "Aruda" +Outfit = (140,77-83-79-95) +Home = [32368,32215,7] +Radius = 99 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Oh, hello, handsome! It's a pleasure to meet you %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Oh, hello %N, your hair looks great! Who did it for you?", Topic=1 +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please be nice and wait a minute. I'll be right with you %N.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you soon." + +"bye" -> "Good bye. I really hope we'll talk again soon.", Idle +"farewell" -> * +"how","are","you" -> "Thank you very much. How kind of you to care about me. I am fine, thank you.", Price=5, Topic=2 +"sell" -> "Sorry, I have nothing to sell.",Price=5, Topic=2 +"job" -> "I do some work now and then. Nothing unusual, though.",Price=5, Topic=2 +"news" -> "You should ask Oswald about news. He loves them." +"name",male -> "I am a little sad, that you seem to have forgotten me, handsome. I am Aruda.",Price=5, Topic=2 +"name",female -> "I am Aruda.",Price=5, Topic=2 +"aruda",male -> "Oh, I like it, how you say my name.",Price=5, Topic=2 +"aruda",female -> "Yes, that's me!",Price=5, Topic=2 +"time" -> Type=2906, Amount=1, "Please don't be so rude to look for the time if you are talking to me.", Topic=3 +"help" -> "I am deeply sorry, I can't help you.",Price=5, Topic=2 +"monster" -> "UH! What a terrifying topic. Please let us speak about something more pleasant, I am a weak and small woman after all.", Price=5, Topic=2 +"dungeon" -> * +"sewer" -> "What gives you the impression, I am the kind of women, you find in sewers?", Price=5, Topic=2 +"god" -> "You should ask about that in one of the temples.", Price=5, Topic=2 +"king" -> "The king, that lives in this fascinating castle? I think he does look kind of cute in his luxurious robes, doesn't he?", Price=10, Topic=2 +"sam",male -> "He is soooo strong! What muscles! What a body! On the other hand, compared to you he looks quite puny.", Price=5, Topic=2 +"sam" -> "He is soooo strong! What muscles! What a body! Did you ask him for a date?", Price=5, Topic=2 +"benjamin" -> "He is a little simple minded but always nice and well dressed.", Price=5, Topic=2 +"gorn" -> "He should really sell some stylish gowns or something like that. We Tibians never get some clothing of the latest fashion. It's a shame.", Price=5, Topic=2 +"quentin" -> "I don't understand this lonely monks. I love company too much to become one. He, he, he!", Price=5, Topic=2 +"bozo" -> "Oh, isn't he funny? I could listen to him the whole day.", Price=5, Topic=2 +"oswald" -> "As far as I know, he is working in the castle." +"rumour" -> "I am a little shy and so don't hear many rumors", Price=5, Topic=2 +"rumor" -> * +"gossip" -> * +"fuck",male -> "Oh, you little devil, stop talking like that! ", Price=20, Topic=2 +"kiss",male -> * +"fuck",female -> "Uhm, let us change the subject, please.", Price=20, Topic=2 +"weapon" -> "I know so little about weapons, so tell me something about weapons, please.", Price=5, Topic=2 +"magic" -> "I believe that love is stronger then all magic, don't you agree?", Price=5, Topic=2 +"thief" -> "Oh, sorry, I have to hurry, bye!", Idle +"theft" -> * +"tibia" -> "I would like to visit the beach more often, but I guess it's too dangerous.", Price=5, Topic=2 +"castle" -> "I love this castle! It's so beautiful.", Price=5, Topic=2 +"muriel" -> "Powerful sorcerers frighten me a little.", Price=5, Topic=2 +"elane" -> "I personally think it's inappropriate for a woman to become a warrior, what do you think about that?", Price=5, Topic=2 +"marvik" -> "Druids seldom visit a town, what do you know about druids?", Price=5, Topic=2 +"gregor" -> "I like brave fighters like him.", Price=5, Topic=2 +"noodles" -> "Oh, he is sooooo cute!", Price=5, Topic=2 +"dog" -> "I like dogs, the little ones at least. Do you like dogs, too?", Price=5, Topic=2 +"poodle" -> * +"excalibug" -> "Oh, I am just a girl and know nothing about magic swords and such things.", Price=10, Topic=2 +"partos" -> "I ... don't know someone named like that.", topic=4 +"yenny" -> "Yenny? I know no Yenny, nor have I ever used that name! You have mistook me with someone else.", Idle + +Topic=1 -> "I would never have guessed that." +Topic=2,CountMoney>=Price -> "Oh, sorry, I was distracted, what did you say?", DeleteMoney +Topic=2,CountMoney "Oh, I just remember I have some work to do, sorry. Bye!", Idle +Topic=3,Count(Type)>=Amount -> "Take some time to talk to me!", Delete(Type) +Topic=4,"spouse" -> "Well ... I might have known him a little .. but there was nothing serious.", Topic=5 +Topic=4,"girlfriend" -> * +Topic=5,"fruit" -> "I remember that grapes were his favourites. He was almost addicted to them." +} diff --git a/data/npc/ashtamor.npc b/data/npc/ashtamor.npc new file mode 100644 index 0000000..8e181ee --- /dev/null +++ b/data/npc/ashtamor.npc @@ -0,0 +1,56 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ashtamor.npc: Krematoriumsbesitzer Ashtamor + +Name = "Ashtamor" +Outfit = (130,19-114-76-114) +Home = [32958,32088,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N, wanderer between the worlds." +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "The time comes for everyone, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you again ... sooner or later, more or less alive." + +"bye" -> "See you again ... sooner or later, more or less alive.", Idle +"job" -> "I consider myself as a guide, a guardian over the souls who transcend the border to another world." +"crematory" -> "Such an ugly word for this wonderful place. It is a door, a portal to a better world than this one is." +"name" -> "What is a name worth in your eyes? And more important: Does the choice of your name decide your further fate? Perhaps we will never know." +"time" -> "It's %T now, but the true question is: How much time is left?" +"fire" -> "The purging force of the fire ... after having been purified, the freed souls will depart with the smoke." +"soul" -> "The essence of life. Source of your very self. While the body is in space and time, the soul exists in time only." +"body" -> "Is the mind an emination of body, or the body an invention by the mind?" +"death" -> "What else does it mean than the loss of your weak physical shell? And isn't the true power in the universe rather mental than physical?" +"venore" -> "You come to this world naked, and leave it this way, so there's no need to hold back your money, especially not in a place like Venore." +"king" -> "Kings, queens ... I've seen them come and go. Everything fades, even the glory and wealth of the richest." +"monster" -> "Oh yes, monsters can grant you a passage to the afterlife also, but it's not a comfortable trip. " +"thanks" -> "'Thank you' ... Words I rarely here these days. Tell me when I might be of service again, %N." +"thank","you" -> * +"help" -> "What help might I offer you except guidance? Would you like me to help you transcend the border to the afterlife?", Topic=1 + +Topic=1,"yes" -> "Are you sure? You might not be able to come back, consider that.", Topic=2 +Topic=1,"no" -> "Come to me when you have changed your mind, wanderer." +Topic=2,"yes" -> EffectOpp(16), EffectMe(14), "Hmm... seems you are not ready yet to let go." +Topic=2,"no" -> "Come to me when you have changed your mind, wanderer." + +"buy" -> "I am offering vases and amphoras, the perfect vessel for dusty remains of whatever sort." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"vase" -> Type=2876, Amount=1, Price=3, "Do you want to buy a vase for %P gold?", Topic=3 +"amphora" -> Type=2893, Amount=1, Price=4, "Do you want to buy an amphora for %P gold?", Topic=3 + +%1,1<%1,"vase" -> Type=2876, Amount=%1, Price=3*%1, "Do you want to buy %A vases for %P gold?", Topic=3 +%1,1<%1,"amphora" -> Type=2893, Amount=%1, Price=4*%1, "Do you want to buy %A amphoras for %P gold?", Topic=3 + + +Topic=3,"yes",CountMoney>=Price -> "May it be of good use to you. ", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Sorry, that's not even enough for a shard of my quality wares." +Topic=3 -> "Do you really want to leave the choice of your future vessel to chance?" +} diff --git a/data/npc/asima.npc b/data/npc/asima.npc new file mode 100644 index 0000000..721a62b --- /dev/null +++ b/data/npc/asima.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# asima.npc: Datenbank fuer die Magiebedarfshändlerin Asima + +Name = "Asima" +Outfit = (138,97-70-94-76) +Home = [33220,32404,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Asima, student of arcane magic and right hand of Shalmar." +"job" -> "Shalmar's ears and eyesight have gotten really bad lately. I'm helping him with his magic store so he can focus on teaching spells." +"time" -> "It's %T right now." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=5 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=5 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=4 +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 + +%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=5 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=5 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=4 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy %A spellbooks for %P gold?", Topic=4 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=6 +"vial" -> * +"flask" -> * + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=6,"yes" -> "You don't have any empty vials." +Topic=6 -> "Hmm, but please keep Tibia litter free." + +} diff --git a/data/npc/asrak.npc b/data/npc/asrak.npc new file mode 100644 index 0000000..5ed7d3f --- /dev/null +++ b/data/npc/asrak.npc @@ -0,0 +1,135 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# asrak.npc: Datenbank für den Minotaurenklingenmeister Asrak + +Name = "Asrak" +Outfit = (29,0-0-0-0) +Home = [32932,32074,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "I welcome you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait in patience, young %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your path be as straight as an arrow." + +"bye" -> "May your path be as straight as an arrow.", Idle +"farewell" -> * +"job" -> "I am the overseer of the pits and the trainer of the gladiators." +"shop" -> * +"name" -> "I am known as Asrak the Ironhoof." +"time" -> "It is %T." +"king" -> "I pledge no allegiance to any king, be it human or minotaurean." +"tibianus" -> * +"venore" -> "The city pays me well and those undisciplined gladiators need my skills and guidance badly." +"gladiator" -> "Those wannabe fighters are weak and most of them are unable to comprehend a higher concept like the Mooh'Tah." +"trainer" -> * +"minotaur" -> "In the ancient wars we lost much because of the rage. The one good thing is we lost our trust in the gods, too." +"gods" -> "By them we were imbued with the rage that almost costed our existence. By them we were used as pawns in wars that were not ours." +"mintwallin" -> "The city is only a shadow of what we could have accomplished without that curse of rage that the gods bestowed upon us." +"rage" -> "Rage is the legacy of Blog, the beast. To overcome it is our primal goal. The Mooh'Tah is our only hope of salvation and perfection." +"guidance" -> "Like all true minotaurean blademasters I am a warrior-philosopher of the Mooh'Tah." +"mooh'tah" -> "The Mooh'Tah teaches us control. It provides you with weapon, armor, and shield. It teaches you harmony and focus." +"harmony" -> "There is an elegant harmony in every thing done right. If you feel the harmony of an action you can sing its song." +"sing" -> "Each harmonic action has it own song. If you can sing it, you are in harmony with that action. This is where the minotaurean battlesongs come from." +"song" -> * +"battlesongs" -> "Each Mooh'Tah master focuses his skills on the harmony of battle. He is one with the song that he's singing with his voice or at least his heart." +"mooh'tah","master" -> "Mooh'Tah masters are the epitome of the minotaurean warrior-philosophers. Full in control, free of rage, focused in perfect harmony with their actions." +"warrior-philosopher" -> * +"general" -> "Your human generals are like their warriors. They lack the focus to be a true warrior." +"army" -> "Your human army might be big, but without skills. They are only sheep to be slaughtered." +"ferumbras" -> "To rely on magic is like to cheat fate. All cheaters will find their just punishment one day, and so will he." +"excalibug" -> "If it's truly a weapon to slay gods it might be worth to be sought for." +"news" -> "Focus on your own life, not on that of others." +"help" -> "I teach worthy warriors the way of the knight." +"monster" -> "Inferior creatures of rage, driven by their primitive urges. Only worthy to be noticed to test one's skills." +"dungeon" -> "The dungeons of your desires and fears are the only ones you really should fear and those you really have to conquer." +"thanks" -> "I hope you learned something." +"thank","you" -> * + +"offer" -> "I offer you the teachings of knighthood and the way of the paladin." +"training" -> * +"do","you","sell" -> "I am not a merchant, but a warrior." +"do","you","have" -> * +"weapon" -> "Make your will your weapon, and your enemies will perish." +"armor" -> "Courage is the only armor that shields you against rage and fear, the greatest dangers you will have to face." +"shield" -> "Your confidence shall be your shield. Nothing can penetrate that defence. No emotion will let you lose your focus." + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +Paladin,"spell" -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell" -> "Sorry, I only teach spells to knights and paladins." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level would you like to learn a spell?", Topic=2 +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "May your path be as straight as an arrow.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 + +Topic=3,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=3,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "May your path be as straight as an arrow.", Idle + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=3 +Topic=3,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 + +Topic=3 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=3 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=4 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=4 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=4 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "Return when you have enough gold." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." +} diff --git a/data/npc/avar.npc b/data/npc/avar.npc new file mode 100644 index 0000000..3a6a29f --- /dev/null +++ b/data/npc/avar.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# avar.npc: Datenbank für den Abenteurer Avar Tar + +Name = "Avar Tar" +Outfit = (73,0-0-0-0) +Home = [33250,31764,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveler %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Only one chat at the same time, sorry." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"name" -> "I am Avar Tar, slayer of monsters, saviour of princesses, and defender of the weak." +"job" -> "I am a professional hero." +"time" -> "It's %T right now." +"king" -> "I am on a quest for the Thaian king ... as usual, of course." +"tibianus" -> * +"quest" -> * +"army" -> "Where the army fails a hero like me is needed." +"ferumbras" -> "I fought him serveral times, sometimes he killed me, sometimes I killed him, I would say we are even right now, but I am getting better and more powerful each day." +"excalibug" -> "I am sure it's hidden in a vault of the Nightmare Knights beneath the Plains of Havoc. I plan an expedition to go there and rout out the Ruthless Seven, but I have to save the world first." +"thais" -> "If I had the time I would restore peace in this once proud city, but there's too much to do before I can start that quest." +"tibia" -> "I've seen it all and done it all ... at least twice." +"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" -> * +} diff --git a/data/npc/azil.npc b/data/npc/azil.npc new file mode 100644 index 0000000..a237b8e --- /dev/null +++ b/data/npc/azil.npc @@ -0,0 +1,105 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Azil.npc: Datenbank für den Rüstungshändler Azil + +Name = "Azil" +Outfit = (129,95-10-12-119) +Home = [33217,32431,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted %N! See the armors: harder than the scales of a dragon, lighter than a feather." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, o honoured customer.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, honoured customer. It was a pleasure to talk to you." + +"bye" -> "Good bye. Come back soon.", Idle +"job" -> "I sell various kinds of masterly crafted armor. The wares I offer are as numerous as the sand of the desert." +"shop" -> * +"name" -> "My name is Azil Ibn Izal." +"time" -> "It's %T right now, o honoured one." +"help" -> "I sell and buy armor, helmets, and shields." +"drefia" -> "O brave one! Before you go there, please make sure that you buy the best armor you can afford." +"thanks" -> "You are welcome, o richest of the wealthiest." +"thank","you" -> * + +"buy" -> "So, what do you need? I sell armor, helmets, shields, and trousers." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are armor, helmets, trousers, and shields." +"weapon" -> "You see me sad, but you have to ask another tradesman for that." +"helmet" -> "I am selling leather helmets, chain helmets, brass helmets, and viking helmets. What do you want?" +"armor" -> "I am selling leather armor, chain armor, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, brass shields, and plate shields. What do you want?" +"trousers" -> "I am selling chain legs and brass legs. What do you need?" +"legs" -> * + +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "Do you want to buy brass legs for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "Do you want to buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "Do you want to buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "Do you want to buy %A brass legs for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "Do you want to sell plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Oh thank you, most generous one. Here are your wares.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, but your purse is as empty as the eye socket of a ghoul." +Topic=1 -> "Maybe we can trade another day." + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your money. It was a pleasure to deal with you.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you don't own one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe we can trade another day." +} diff --git a/data/npc/baaleal.npc b/data/npc/baaleal.npc new file mode 100644 index 0000000..9259467 --- /dev/null +++ b/data/npc/baaleal.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# baaleal.npc: Datenbank für den Efreetgeneral Baa'leal + +Name = "Baa'leal" +Outfit = (51,0-0-0-0) +Home = [33048,32620,4] +Radius = 2 + +Behaviour = { + +ADDRESS,QuestValue(285)=0,"djanni'hah$",! -> "You know the code human! Very well then... What do you want, %N?",SetQuestValue(285,1) +ADDRESS,QuestValue(285)=0,! -> "A human! TAKE THIS!",SetQuestValue(285,1), Burning(150,4), EffectOpp(5), EffectMe(8),Idle + +ADDRESS,"hello$",QuestValue(278)=3,! -> "You are still alive, %N? Well, what do you want?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=3,! -> "Can't you see I am already talking to somebody here, %N? You civilians don't understand the concept of discipline at all, do you!", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP + +VANISH -> "Hail Malor!" + +"bye" -> "Stand down, soldier!", Idle +"farewell" -> * +"name" -> "I'm general Baa'leal. What do you want in Mal'ouquah?" +"general" -> * +"baa'leal" -> "That is GENERAL Baa'leal for you, human." +"job" -> "I am commander-in-chief of the armed forces of the UDLA, all branches of service. ...", + "Hence I'm responsible for all operations in the enemy's territory." +"udla" -> "Yes. The United Djinn Liberation Army. ...", + "The title has been given to our valiant armed forces in order to stress both the revolutionary focus of our agenda and the universalist nature of our political approach. ...", + "Don't ask me what that means. Wasn't my idea." + +"ubaid",QuestValue(286)=0,! -> "Ubaid told you to speak with me? Hmmm... maybe there is something you could help us with. Are you interested, human?",Topic=1 +"work",QuestValue(286)=0,! -> * +"operation",QuestValue(286)=0,! -> "Each mission and operation is a crucial step towards our victory! ...", + "Now that we speak of it ...", + "Since you are no djinn, there is something you could help us with. Are you interested, human?",Topic=1 +"mission",QuestValue(286)=0,! -> * + +Topic=1,"yes" -> "Well ... All right. You may only be a human, but you do seem to have the right spirit. ...", + "Listen! Since our base of operations is set in this isolated spot we depend on supplies from outside. These supplies are crucial for us to win the war. ...", + "Unfortunately, it has happened that some of our supplies have disappeared on their way to this fortress. At first we thought it was the Marid, but intelligence reports suggest a different explanation. ...", + "We now believe that a human was behind the theft! ...", + "His identity is still unknown but we have been told that the thief fled to the human settlement called Carlin. I want you to find him and report back to me. Nobody messes with the Efreet and lives to tell the tale! ...", + "Now go! Travel to the northern city Carlin! Keep your eyes open and look around for something that might give you a clue!", SetQuestValue(286,1) +Topic=1 -> "After all, you're just a human." + +"operation",QuestValue(286)>0,QuestValue(286)<3 -> "Did you find the thief of our supplies?", Topic=2 +"mission",QuestValue(286)>0,QuestValue(286)<3 -> * +"work",QuestValue(286)>0,QuestValue(286)<3 -> * +"thief",QuestValue(286)>0,QuestValue(286)<3 -> * + +Topic=2,"yes" -> "Finally! What is his name then?", Topic=3 +Topic=2 -> "Then go to Carlin and search for him! Look for something that might give you a clue!" + +Topic=3,"partos",QuestValue(286)=2,! -> "You found the thief! Excellent work, soldier! You are doing well - for a human, that is. Here - take this as a reward. ...", + "Since you have proven to be a capable soldier, we have another mission for you. ...", + "If you are interested go to Alesar and ask him about it.", Amount=6, Create(3035), SetQuestValue(286,3) +Topic=3,! -> "Hmmm... I don't think so. Return to Carlin and continue your search!" + +"operation",QuestValue(286)=3 -> "Did you already talk to Alesar? He has another mission for you!" +"mission",QuestValue(286)=3 -> * + +"mal'ouquah" -> "At the moment Mal'ouquah is our headquarter. However, I am already working on a cunning plan to move our base of operations deep into the enemy's territory." +"ashta'daramai" -> "Ashta'daramai is the enemy's base of operations. I am looking forward to the moment when we raise our flag there!" +"gabel" -> "He is weak. Much too weak to be our leader." +"king" -> "The UDLA does not serve a king because there isn't any. Of course, that is bound to change." +"djinn" -> "We are a race of warriors! We Efreets are destined to rule and to conquer." +"efreet" -> "We are the true djinn! We do not live in denial of our true nature like those damn liberals, the Marid." +"marid" -> "Nothing but a bunch of mealy-mouthed, mollycoddled wimps and milksops the lot of them. They may be superior in numbers, but we will win anyway because of our superior strategic thinking." +"malor" -> "Hail to our great leader!" +"human" -> "No offence, but your race is weak. You lack both the physical strength and the true warrior spirit. And worst of all, you have no strategic thinking." +"zathroth" -> "I understand he created us. Must have been a great general." +"tibia" -> "It is our mission to achieve total and decisive dominion of this world within two years. Well perhaps ... three. Always be realistic, that's what I say." +"daraman" -> "Damn that liberal peacenik, that treacherous mealy-mouthed double-faced good-for-nothing surrender monkey! ...", + "He has infected this proud people's minds with his peace-for-all blabber." +"darashia" -> "The humans living in the northern deserts used to be nomads. Even though they are just humans they used to be respectable fighters. ...", + "However, now they are living in this city they have grown fat and decadent. They will be easy prey." +"scarab" -> "Impressive animals. I have this idea of training them as battle steeds. Imagine this: Djinns mounted on scarabs! With a battalion of those I would crush the enemy in the blink of an eye!" +"edron" -> "They say the humans have built some big cities over there. I am looking forward to see them burn." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That old city has some impressive defensive structures. But I swear I will bring it down one day... I have a cunning plan already! ...", + "I am thinking of a huge wooden camel." +"pharaoh" -> "Ankrahmun's pharaoh apparently believes himself to be some sort of god. Ah well. A solid blow with my scimitar will bring him back to earth soon enough!" +"palace" -> "I suppose the palace is where the pharaoh resides. I have a distinct feeling I shall see it burn rather soon." +"ascension" -> "Apparently, ascension is what the followers of the pharaoh are after. No idea what exactly that is, though." +"rah" -> "Spare me that pseudo-theological hogwash." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "It was an excellent idea to build our headquarter in the mountains of kha'zeel. Easy to defend, you know. Too bad the enemy had the same idea." +"kha'labal" -> "Kha'labal? Yes, it was me who devastated it. Couldn't leave it to the enemy, you see? We had to destroy it in order to save it!" +"war" -> "War is the father of things, and I live and breathe it. Ok, it's a tad bit silly that we are forced to fight against our own kind, but as a good soldier I will do my duty! ...", + "And if I hear anybody talking about 'peace' he will be court-martialled and summarily executed! Or vice versa!" +"melchior" -> "Melchior! I remember that greedy little civilian. I would have court-martialled him, but I suppose it is just as well the way it is." +"alesar" -> "Ah yes, Alesar! Excellent smith, that man!" +"fa'hradin" -> "He is Gabel's lieutenant and confidant. He is a powerful wizard, one has to admit that - and that's the only reason he is still alive. Without all his magical mumbo jumbo we would have long since won this war." +"lamp" -> "We sleep in those lamps. I like them - they are small and functional. We do not need cozy beds and fluffy duvets like decadent humans." + + + + + +#"mission", Questvalue(###)=### -> "A volunteer, hm? Well ... All right. You may only be a human, but you do seem to have the right spirit, and I like that. That's what we need around here!","Listen. Since our base of operations is set in this isolated spot we depend on supplies from outside. These supplies are crucial for us to win the war.","Unfortunately, it has happened that some of our supplies have disappeared on their way to this fortress. At first we thought it was the Marid, but intelligence reports suggest a different explanation. We now believe that a group of humans was behind the theft.","Unfortunately, we do not have much further specific information. All we know is that the thieves' hideout is somewhere in a northern city. However, you are a human, so you might stand a good chance to find those thieving jerks. I want them punished. Nobody messes with the Efreet and lives to tell the tale!","Now go! Travel to the northern cities! Look around for something that might give you a clue!", SetQuestValue +#"mission", Questvalue(###)=### -> "You found the thieves? Excellent work, soldier! You are doing well - for a human, that is. Here - take this. Since you are not a regular member of the UDLA you have deserve some compensation.", Create (###,###), SetQuestValue(###,###) +#"mission", Questvalue(###)=### -> "Still feeling adventurous, are we? Well, as I matter of fact there is something else you could do for us. Something very special.","Listen: We have sent a spy Ashta'Daramai, our enemy's fortress. He was on a mission to steal certain documents for us. Unfortunately, we have lost contact with him.","We must find out what happened to him and, more importantly, to the documents. Go to the to the fortress and try to find him. Above all, find the documents and bring them to me.","A word of advice: Our spy entered the fortress through a network of underground tunnels. Perhaps you should sneak into Ashta'Daramai using the same route. That way he should be easy to find.","Go now and don't come back without the documents.", SetQuestValue (###,###) +#"mission", Questvalue(###)=### -> "Well, blast my buttocks with a blunderbuss###! The documents! Outstanding work, soldier! Malor will be pleased!","It appears you do deserve our trust after all. Tell you what, soldier - I will order that stubborn Alesar to fully cooperate with you. He's got some pretty nice items to sell. He won't like it, of course, but I'm sure he will do as he is told. He is a namby-pamby civilian after all.","Oh, and ask him about a mission, too. I think he and Malor were talking about a plan.", SetQuestValue(###,###) + +} diff --git a/data/npc/bambi.npc b/data/npc/bambi.npc new file mode 100644 index 0000000..c30eb2f --- /dev/null +++ b/data/npc/bambi.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bambi.npc: Datenbank für die Wächterin Bambi Bonecrusher (Carlin) + +Name = "Bambi Bonecrusher" +Outfit = (139,96-19-68-95) +Home = [32341,31749,7] +Radius = 2 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/data/npc/bansheequeen.npc b/data/npc/bansheequeen.npc new file mode 100644 index 0000000..b7aff1c --- /dev/null +++ b/data/npc/bansheequeen.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bansheequeen.npc: Datenbank für die Bansheequeen + +Name = "The Queen of the Banshee" +Outfit = (78,0-0-0-0) +Home = [32260,31863,14] +Radius = 6 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear visitor. Come and stay ... a while." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait as patiently as death is waiting for you!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes, flee from death. But know it shall be always one step behind you." + + +"bye" -> "We will meet again.", Idle +"farewell" -> * + +"name" -> "It hurts me to even think about my mortal past. Its long lost and forgotten. So don't ask me about it!" +"job" -> "It is my curse to be the eternal guardian of this ancient place." +"place" -> "It served as a temple, a source of power and ... as a sender for an ancient race in time long gone by and forgotten." +"race" -> "The race that built this edifice came to this place from the stars. They ran from an enemy even more horrible than even themselves. But they carried the seed of their own destruction in them." +"seed" -> "This ancient race was annihilated by its own doings, that's all I know. Aeons have passed since then, but the sheer presence of this complex is still defiling and desecrating this area." +"destruction" -> * +"complex" -> "Its constructors were too strange for you or even me to understand. We cannot know what this ... thing they have built was supposed to be good for. All I can feel is a constant twisting and binding of souls, though that is probably only a side-effect." +"ghostlands" -> "The place you know as the Ghostlands had a different name once ... and many names thereafter. Too many for me to remember them all." +"banshee" -> "They are my maidens. They give me comfort in my eternal vigil over the last seal." +"seal" -> "I am the guardian of the SEVENTH and final seal. The seal to open the last door before ... but perhaps it is better you see it with your own eyes." +"guardian" -> * +"seventh",level<60,! -> "You are not experienced enough to master the challenges ahead or to receive knowledge about the seventh seal. Go and learn more before asking me again." +"seventh",level>59,! -> "If you have passed the first six seals and entered the blue fires that lead to the chamber of the seal you might receive my kiss ... It will open the last seal. Do you think you are ready?", topic=2 +"last" -> * + +"kiss",PZBlock,! -> "You have spilled too much blood recently and the dead are hungry for your soul. Perhaps return when you regained you inner balance." + +"kiss",topic=8 , QuestValue(11) < 1 -> "Are you prepared to receive my kiss, even though this will mean that your death as well as a part of your soul will forever belong to me, my dear?", Topic=1 + +"kiss", QuestValue(11) > 0 -> "You have already received my kiss. You should know better then to ask for it." +"kiss" -> "To receive my kiss you have to pass all other seals first." +"yes",topic=1 -> "So be it! Hmmmmmm...",SetQuestValue(11,1),SetQuestValue(12,QuestValue(12)+1),Teleport(32202,31812,8), EffectOpp(14) +"no",topic=1 -> "Perhaps it is the better choice for you, my dear." + +"yes",topic=2,QuestValue(4)=1 -> "Yessss, I can sense you have passed the seal of sacrifice. Have you passed any other seal yet?", topic=3 +"yes",topic=2,QuestValue(4)<1 -> "You have not passed the seal of sacrifice yet. Return to me when you are better prepared." +"no",topic=2 -> "Then try to be better prepared next time we meet." + +"yes",topic=3,QuestValue(5)=1 -> "I sense you have passed the hidden seal as well. Have you passed any other seal yet?", topic=4 +"yes",topic=3,QuestValue(5)<1 -> "You have not found the hidden seal yet. Return when you are better prepared." +"no",topic=3 -> "Then try to be better prepared next time we meet." + + +"yes",topic=4,QuestValue(6)=1 -> "Oh yes, you have braved the plagueseal. Have you passed any other seal yet?", topic=5 +"yes",topic=4,QuestValue(6)<1 -> "You have not faced the plagueseal yet. Return to me when you are better prepared." +"no",topic=4 -> "Then try to be better prepared next time we meet." + + +"yes",topic=5,QuestValue(7)=1 -> "Ah, I can sense the power of the seal of demonrage burning in your heart. Have you passed any other seal yet?", topic=6 +"yes",topic=5,QuestValue(7)<1 -> "You are not filled with the fury of the imprisoned demon. Return when you are better prepared." +"no",topic=5 -> "Then try to be better prepared next time we meet." + +"yes",topic=6,QuestValue(9)=1 -> "So, you have managed to pass the seal of the true path. Have you passed any other seal yet?", topic=7 +"yes",topic=6,QuestValue(9)<1 -> "You have not found your true path yet. Return when you are better prepared." +"no",topic=6 -> "Then try to be better prepared next time we meet." + +"yes",topic=7,QuestValue(10)=1 -> "I see! You have mastered the seal of logic. You have made the sacrifice, you have seen the unseen, you possess fortitude, you have filled yourself with power and found your path. You may ask me for my kiss now.", topic=8 +"yes",topic=7,QuestValue(10)<1 -> "You have not found your true path yet. Return to meh when you are better prepared." +"no",topic=7 -> "Then try to be better prepared next time we meet." + + + +"spectral","dress" -> "Your wish for a spectral dress is silly. Allthough I will grant you the permission to take one. My maidens left one in a box in a room, directly south of here.",SetQuestValue(327,1) +} diff --git a/data/npc/barbara.npc b/data/npc/barbara.npc new file mode 100644 index 0000000..24e56f5 --- /dev/null +++ b/data/npc/barbara.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# barbara.npc: Datenbank für die Wachfrau Barbara + +Name = "Barbara" +Outfit = (139,78-52-64-115) +Home = [32320,31752,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hail$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"salutations$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","queen",! -> "Wait for your audience %N!" +BUSY,"hail$","queen",! -> "Wait for your audience %N!" +BUSY,"salutations$","queen",! -> "Wait for your audience %N!" +BUSY,"hi$","queen",! -> "Wait for your audience %N!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/data/npc/bashira.npc b/data/npc/bashira.npc new file mode 100644 index 0000000..492d83f --- /dev/null +++ b/data/npc/bashira.npc @@ -0,0 +1,126 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bashira.npc: Datenbank fuer die Haendlerin Bashira (Elfenstadt) + +Name = "Bashira" +Outfit = (144,78-62-97-76) +Home = [32669,31655,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait one moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I sell various equipment and buy some stuff." +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, parchments, documents, watches, various sources of light, fishing rods and sixpacks of worms." +"goods" -> * +"light" -> "I sell torches, candelabra, and oil." +"name" -> "I am Bashira Darkmark." + +"carlin" -> "Carlin has some capable fighters, allthough they lack the grace of an elf." +"thais" -> "The people of thais boast about their mighty kingdom, but eventually their short lives will doom everything they buld." +"venore" -> "Their merchants have no patience and all to fast they loose their masks of friedlyness." +"roderick" -> "His presence here is a waste of space and talking to or even about him a waste of time." +"olrik" -> "He is quite amusing for a human." + +"elves" -> "That's our race." +"dwarfs" -> "They have some talent in mining and smithing." +"humans" -> "They have nothing to give us." +"troll" -> "They are lazy and clumsy. We should use dwarfs instead." + +"cenath" -> "Their magic is almost as impressive as their egos." +"kuridai" -> "Without us and our tools nothing would work in this town." +"deraisim" -> "Useless leafeaters." +"abdaisim" -> "They left; perhaps we should do that, too." +"teshial" -> "A stupid Cenath-myth." +"ferumbras" -> "He may scare the treedwellers or the big-mouthes above, but not the Kuridai." +"crunor" -> "One god of many. They are all alike and of no use." + +"time" -> "Buy a watch." +"food" -> "I am not dealing with food." + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"bag" -> Type=2857, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2865, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=2 +"waterskin" -> Type=2901, Data=1, Amount=1, Price=10, "Do you want to buy a waterskin for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"bucket" -> Type=2873, Data=0, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Data=0, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelab" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2857, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2865, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=2 +%1,1<%1,"waterskin" -> Type=2901, Data=1, Amount=%1, Price=10*%1, "Do you want to buy %A water skins for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Data=0, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Data=0, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have so much money." +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Here it is. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "You don't have so much money." +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you don't have one." +Topic=3 -> "Maybe next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep Tibia litter free." + +"buy" -> "I have shovels, picks, scythes, bags, ropes, backpacks, plates, scrolls, watches, some lightsources, and other stuff." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"stuff" -> "Water hoses, pitchforks, presents, buckets, bottles, and the like." +} diff --git a/data/npc/basilisk.npc b/data/npc/basilisk.npc new file mode 100644 index 0000000..ddd232e --- /dev/null +++ b/data/npc/basilisk.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# basilisk.npc: Datenbank für den Basilisken + +Name = "Basilisk" +Outfit = (28,0-0-0-0) +Home = [32641,31943,15] +Radius = 2 + +Behaviour = { +ADDRESS,"a",! -> EffectMe(9), Idle +ADDRESS,"b",! -> * +ADDRESS,"c",! -> * +ADDRESS,"d",! -> * +ADDRESS,"e",! -> * + +ADDRESS,"f",! -> EffectMe(13), Idle +ADDRESS,"g",! -> * +ADDRESS,"h",! -> * + +ADDRESS,"i",! -> EffectMe(14), Idle +ADDRESS,"j",! -> * +ADDRESS,"k",! -> * +ADDRESS,"l",! -> * +ADDRESS,"m",! -> * + +ADDRESS,"n",! -> EffectMe(15), Idle +ADDRESS,"o",! -> * +ADDRESS,"p",! -> * +ADDRESS,"r",! -> * +ADDRESS,"s",! -> * + +ADDRESS,"t",! -> EffectMe(17), Idle +ADDRESS,"u",! -> * +ADDRESS,"v",! -> * +ADDRESS,"w",! -> * +ADDRESS,"y",! -> * + +ADDRESS,! -> Idle + +BUSY,! -> NOP + +VANISH,! -> NOP +} diff --git a/data/npc/baxter.npc b/data/npc/baxter.npc new file mode 100644 index 0000000..60249d3 --- /dev/null +++ b/data/npc/baxter.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# baxter.npc: Datenbank für den Burgwächter Baxter + +Name = "Baxter" +Outfit = (131,96-29-29-115) +Home = [32322,32188,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "LONG LIVE KING TIBIANUS!" +ADDRESS,"hi$",! -> "LONG LIVE KING TIBIANUS!" +ADDRESS,! -> Idle +BUSY,"hello$" -> "Cant you see I am busy, eh?!." +BUSY,"hi$" -> "Cant you see I am busy, eh?!." +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "It is rumoured that Ferumbras is planning a new attack on town." +"how","are","you"-> "I am healthy and vigilant." +"sell" -> "Visit Tibia's shopkeepers to buy their fine wares." +"king" -> "King Tibianus III is our wise and just leader!" +"leader" -> * +"job" -> "I am a proud member of the king's army. It is my duty to guard the castle." +"army" -> "Our brave army, which protects our city, consists of three battlegroups." +"guard" -> * +"battlegroup" -> "There are the dogs of war, the red guards, and the silver guards." +"castle" -> "His Royal Highness ordered the castle to be open for all his subjects." +"subject" -> "We all live under the benevolent guidance of our king." +"dogs","of","war"-> "They are our main army." +"red","guard" -> "They are our special forces. Some serve as cityguards, others as secret police." +"secret","police"-> "Ask a higher offical about that." +"silver","guard" -> "The best sorcerers, paladins, knights, or druids of our forces are choosen to serve as silver guards. They are the bodyguards of the king." +"city" -> "Now that the king returned, we will clean the city from all scum." +"scum" -> "To much scum roams our streets in our days, the red guards will take care of them." +"stutch" -> "He is soldier in the silver guard." +"harsky" -> "He is soldier in the silver guard." +"bozo" -> "The royal jester. I dont think he is funny." +"sam" -> "He is a fine blacksmith. Almost all our weapons are made by him." +"gorn" -> "An old friend of mine. He was once a great warrior and adventurer, now he is running a shop." +"benjamin" -> "He was one of the king's best generals, now he is a bit ...uhm... forgetful." +"excalibug" -> "Gorn and I searched for this weapon in the darkest corners of each dungeon, but found nothing." +"partos" -> "He was wanted for a long time and got caught stealing some time ago.", Topic=1 +Topic=1,"fruit" -> "I understand he was stealing some fruit, he is obsessed with, and got incautious." +Topic=1 -> "What has this to do with this Partos guy?" +"chester" -> "This man is paranoid, but I guess that is useful in his job." +"tbi" -> "There is almost nothing known about that organization." +"work" -> "We have a rat problem in the sewers. In the name of our glorious king I am paying 1 blinking piece of gold for every freshly killed rat you bring to me." +"mission" -> * +"quest" -> * +"rat" -> Type=3994, Amount=1, Price=1, "Do you bring a freshly killed rat for a bounty of %P gold?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=1*%1, "Do you want to deliver me %A rats for a bounty of %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your reward. You will become a great warrior some day.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Look like it wasn't as dead as you thought ... it's gone." +Topic=2 -> "Come on. Don't waste my time with your jests." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/data/npc/beatrice.npc b/data/npc/beatrice.npc new file mode 100644 index 0000000..43e6b09 --- /dev/null +++ b/data/npc/beatrice.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# beatrice.npc: Datenbank fuer die Ausruestungshaendlerin Beatrice + +Name = "Beatrice" +Outfit = (136,96-102-69-95) +Home = [33214,31803,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hiho, and ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You're next, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you later." + +"bye" -> "See you later.", Idle +"name" -> "I am called Beatrice." +"job" -> "My job is to sell all kind of useful equipment." +"time" -> "It's %T right now." +"king" -> "I have seen him once. What a handsome man he is." +"tibianus" -> * +"army" -> "I supply them with some basic stuff." +"ferumbras" -> "I vaguely remember that name." +"excalibug" -> "A myth like the screwdriver of Kurik or the endless vial of manafluid." +"thais" -> "We are no longer in need to be supplied from there." +"tibia" -> "I don't like travelling much. I prefer to live in the safety of our city." +"carlin" -> "Though they rebelled against our king it's said that the city is very lovely." +"edron" -> "It's the best place to live at." +"news" -> "There are always rumors about the dangers in the far north of Edron." +"rumors" -> * + +"offer" -> "My inventory is large, just have a look at the blackboard." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2861, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2869, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy a watch for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"lamp" -> Type=2914, Amount=1, Price=8, "Do you want to buy a lamp for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"basket" -> Type=2855, Amount=1, Price=6, "Do you want to buy a basket for %P gold?", Topic=1 +"trap" -> Type=3481, Amount=1, Price=280, "Do you want to buy a trap for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2861, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2869, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"lamp" -> Type=2914, Amount=%1, Price=8*%1, "Do you want to buy %A lamps for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"basket" -> Type=2855, Amount=%1, Price=6*%1, "Do you want to buy %A baskets for %P gold?", Topic=1 +%1,1<%1,"trap" -> Type=3481, Amount=%1, Price=280*%1, "Do you want to buy %A traps for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 + + +"sell","watch" -> Type=2906, Amount=1, Price=6, "Do you want to sell a watch for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=15, "Do you want to sell a rope for %P gold?", Topic=2 +"sell","scythe" -> Type=3453, Amount=1, Price=12, "Do you want to sell a scythe for %P gold?", Topic=2 +"sell","pick" -> Type=3456, Amount=1, Price=15, "Do you want to sell a pick for %P gold?", Topic=2 +"sell","shovel" -> Type=3457, Amount=1, Price=8, "Do you want to sell a shovel for %P gold?", Topic=2 +"sell","mirror" -> Type=3463, Amount=1, Price=10, "Do you want to sell a mirror for %P gold?", Topic=2 +"sell","rod" -> Type=3483, Amount=1, Price=40, "Do you want to sell a fishing rod for %P gold?", Topic=2 +"sell","inkwell" -> Type=3509, Amount=1, Price=8, "Do you want to sell an inkwell for %P gold?", Topic=2 +"sell","sickle" -> Type=3293, Amount=1, Price=3, "Do you want to sell a sickle for %P gold?", Topic=2 +"sell","crowbar" -> Type=3304, Amount=1, Price=50, "Do you want to sell a crowbar for %P gold?", Topic=2 +"sell","trap" -> Type=3481, Amount=1, Price=75, "Do you want to sell a trap for %P gold?", Topic=2 + +"sell",%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=6*%1, "Do you want to sell %A watches for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=15*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 +"sell",%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to sell %A scythes for %P gold?", Topic=2 +"sell",%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=15*%1, "Do you want to sell %A picks for %P gold?", Topic=2 +"sell",%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=8*%1, "Do you want to sell %A shovels for %P gold?", Topic=2 +"sell",%1,1<%1,"mirror" -> Type=3463, Amount=%1, Price=10*%1, "Do you want to sell %A mirrors for %P gold?", Topic=2 +"sell",%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=40*%1, "Do you want to sell %A fishing rods for %P gold?", Topic=2 +"sell",%1,1<%1,"inkwell" -> Type=3509, Amount=%1, Price=8*%1, "Do you want to sell %A inkwells for %P gold?", Topic=2 +"sell",%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=3*%1, "Do you want to sell %A sickles for %P gold?", Topic=2 +"sell",%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=50*%1, "Do you want to sell %A crowbars for %P gold?", Topic=2 +"sell",%1,1<%1,"trap" -> Type=3481, Amount=%1, Price=75*%1, "Do you want to sell %A traps for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/beholder.npc b/data/npc/beholder.npc new file mode 100644 index 0000000..4cdf289 --- /dev/null +++ b/data/npc/beholder.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# beholder.npc: Datenbank für den Bibliotheksbeholder (Elfenstadt) + +Name = "A Wrinkled Beholder" +Outfit = (17,0-0-0-0) +Home = [32788,31690,13] +Radius = 10 + +Behaviour = { +ADDRESS,"hello$",! -> "What is this? An optically challenged entity called %N. How fascinating!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait. I will eat you later, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Strange entity. I will record this encounter." + +"bye" -> "Wait right there. I will eat you after writing down what I found out.", Idle +"farewell" -> * +"job" -> "I am the great librarian." +"name" -> "I am 486486 and NOT 'Blinky' as some people called me ... before they died." +"blinky" -> "How interesting you are that stupid. Let me apply this on you and see how long you last", Burning(10,25), EffectOpp(5), EffectMe(8) +"tibia" -> "It's 1, not 'Tibia', silly." +"ab'dendriel" -> "I heard that elves moved in upstairs." +"elves" -> "These fools and their superstitious life cult don't understand anything of importance." +"humans" -> "Good tools to work with ... After their death, that is." +"orcs" -> "Noisy pests." +"minotaurs" -> "Their mages are so close to the truth. Closer then they know and closer then it's good for them." +"god" -> "They will mourn the day they abandoned us." +"death" -> "Yes, yes, I will kill you soon enough, now let me continue my investigation on you." +"numbers" -> "Numbers are essential. They are the secret behind the scenes. If you are a master of mathematics you are a master over life and death." +"library" -> "It's a fine library, isn't it?" +"books" -> "Our books are written in 469, of course you can't understand them." +"469" -> "The language of my kind. Superior to any other language and only to be spoken by entities with enough eyes to blink it." +"cyclops" -> "Uglyness incarnate. One eye! Imagine that! Horrible!" +"excalibug" -> "Only inferior species need weapons." +} diff --git a/data/npc/benjamin.npc b/data/npc/benjamin.npc new file mode 100644 index 0000000..e819dd8 --- /dev/null +++ b/data/npc/benjamin.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# benjamin.npc: Datenbank für den Postmann Benjamin + +Name = "Benjamin" +Outfit = (128,116-79-117-76) +Home = [32350,32219,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello. How may I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking to a customer, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was a pleasure to help you %N." + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + + +"kevin" -> "That name sounds familiar... who might that be..." +"postner" -> * +"postmasters","guild" -> "Hm, I think I heard about that guild... oh wait, I am a member!" +"join" -> "Uh... oh... Uhm... Join what?" +"headquarter" -> "Its just... I mean... there was that road, oh yes, its that house at that road." + + +"measurements",QuestValue(234)>0,QuestValue(236)<1 -> "Oh they dont change that much since in the old days as... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(236,1) + +"job" -> "I am working here at the post office. If you have questions about the Royal Tibia Mail System or the depots ask me." +"office" -> "I am always in my office. You are welcome at any time." +"name" -> "My name is Benjamin." +"time" -> "Now it's %T. Maybe you want to buy a watch?" +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all tibian citizens. Hail our king!" +"king" -> "Oops, the king? I... can't remember his name..." +"tibianus" -> "Ah, King Tibianus, our wise ruler. He is sick for some time, isn't he?" +"quentin" -> "Ooooh, nice man, visits me often... I think." +"lynda" -> "She is SO pretty!" +"harkath" -> "Oh, young Harkath will be a fine warrior some day." +"army" -> "TO THE ARMS! MAN THE WALLS! FERUMBRAS IS NEAR!", Idle +"ferumbras" -> * +"general" -> * +"sam" -> "Ham? No thanks, I ate fish already." +"frodo" -> "Frodo... Frodo... ? Uhm... isn't that the man that brings me food at lunchtime?" +"gorn" -> "He sells equipment." +"elane" -> "Oh, she lives next door. I think she's a dentist, I sometimes hear some cries." +"muriel" -> "This Muriel has a lot of correspondence." +"gregor" -> "Never heared of him." +"marvik" -> "He is always talking of healing me but I am fine... I fear he is a little nuts, poor man." +"bozo" -> "He hangs around here quite often. He claimes, I inspire him." +"baxter" -> "This naughty child, always stealing apples!" +"sherry" -> "I don't drink alcohol while on duty." +"lugri" -> "NO! NO! NO! GO AWAY!.", Idle +"excalibug" -> "I can't remember that someone named like that lives here." +"news" -> "Sorry, I don't read the letters we transmit." +"thais" -> "This is the town you are currently in." +"carlin" -> "You can sent letters and parcels to Carlin." +"xodet" -> "The young sorcerer is a good businessman." +"quero" -> "I love his music! He is my best friend and I visit him as often as I can." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/bezil.npc b/data/npc/bezil.npc new file mode 100644 index 0000000..1927d62 --- /dev/null +++ b/data/npc/bezil.npc @@ -0,0 +1,114 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bezil.npc: Datenbank für die Händlerin Bezil + +Name = "Bezil" +Outfit = (160,116-79-117-57) +Home = [32657,31909,9] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$","bezil",! -> "Hiho, Bezil at your service, %N." +ADDRESS,"hi$","bezil",! -> * +ADDRESS,"hiho$","bezil",! -> * +ADDRESS,"hello$",! -> "Are you talking to me, %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","bezil",! -> "Hey, I am busy. I'll be with you in a minute, %N.", Queue +BUSY,"hi$","bezil",! -> * +BUSY,"hiho$","bezil",! -> * +BUSY,"hello$",! -> "Are you talking to me, %N?", Idle +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"hello$","nezil" -> "Good bye.", Idle +"hi$","nezil" -> * +"hiho$","nezil" -> * +"bye" -> * +"farewell" -> * +"job" -> "We sell equipment of all kinds. Is there anything you need?" +"equipment" -> "We sell shovels, picks, scythes, bags, ropes, backpacks, cups, scrolls, documents, parchments, and watches. We also sell lightsources." +"goods" -> * +"light" -> "We sell torches, candlesticks, candelabra, and oil." +"name" -> "I am Bezil Coinbiter, daughter of Earth, of the Molten Rocks. I and my bro' Nezil are selling stuff, ye' know?" +"nezil" -> "He's my bro'." +"time" -> "I think it's about %T. If you'd bought a watch you'd know for sure." +"food" -> "Sorry, visit the Jolly Axeman Tavern for that." + +"goods" -> "Let me see ... we have shovels, picks, scythes, bags, ropes, backpacks, scrolls, watches, some lightsources, fishing rods, sixpacks of worms and other stuff." +"stuff" -> "Oh, things like crowbars, water hoses, presents, buckets, bottles, and the like." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2862, Amount=1, Price=4, "Do you wanna buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you wanna buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you wanna buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you wanna buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=40, "Do you wanna buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you wanna buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you wanna buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you wanna buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you wanna buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you wanna buy a dwarfensteel crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you wanna buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you wanna buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you wanna buy a bottle for %P gold?", Topic=1 +"water","hose" -> Type=2901, Data=1, Amount=1, Price=10, "Do you wanna buy a water hose for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you wanna buy oil for %P gold?", Topic=2 + + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2862, Amount=%1, Price=4*%1, "Do you wanna buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you wanna buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you wanna buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2870, Amount=%1, Price=10*%1, "Do you wanna buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=40*%1, "Do you wanna buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you wanna buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you wanna buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you wanna buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you wanna buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you wanna buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you wanna buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you wanna buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you wanna buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Data=1, Amount=%1, Price=10*%1, "Do you wanna buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here, catch it!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Nice joke, pauper!" +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Nice joke, pauper!" +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you are not having one." +Topic=3 -> "Maybe next time." + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." +} diff --git a/data/npc/bigben.npc b/data/npc/bigben.npc new file mode 100644 index 0000000..86868ff --- /dev/null +++ b/data/npc/bigben.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bigben.npc: Datenbank für den Zyklopenschmied Ben (Elfenstadt) + +Name = "A Sweaty Cyclops" +Outfit = (22,0-0-0-0) +Home = [32697,31674,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hum Humm! Welcume lil' %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N waits. Me talks.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hum Humm." + +"bye" -> "Good bye lil' one.", Idle +"farewell" -> * +"job" -> "I am smith." +"name" -> "I called Bencthyclthrtrprr by me people. Lil' ones me call Big Ben." + +"tibia" -> "One day I'll go and look." +"ab'dendriel" -> "Me parents live here before town was. Me not care about lil' ones." +"big","old" -> "Mountain in south. Lil' lil' ones living there." +"elves" -> "Me not fight them, they not fight me." +"humans" -> "Always asking me for stuff they can't afford." +"orcs" -> "Silly ones. Not talk much. Always screaming and hitting." +"minotaurs" -> "They were friend with me parents. Long before elves here, they often made visit. No longer come here." +"dwarfs" -> "Lil' lil' ones are so fun. We often chat." +"lil","lil" -> * +"god" -> "You shut up. Me not want to hear." +"smith" -> "Working steel is my profession." +"steel" -> "Manny kinds of. Like Mesh Kaha Rogh, Za'Kalortith, Uth'Byth, Uth'Morc, Uth'Amon, Uth'Maer, Uth'Doon, and Zatragil" + +"Mesh","Kaha","Rogh" -> "Steel that is singing when forged. No one knows where find today." +"Za'Kalortith" -> "It's evil. Demon iron is. No good cyclops goes where you can find and need evil flame to melt." +"Uth'Byth" -> "Not good to make stuff off. Bad steel it is. But eating magic, so useful is." +"Uth'Morc" -> "Lil' ones it thieves' steel call sometimes. It's dark and making not much noise." +"Uth'Amon" -> "Brigthsteel is. Much art made with it. Sorcerers to lazy and afraid to enchant much." +"Uth'Maer" -> "Heartiron from heart of big old mountain, found very deep. Lil' lil ones fiercely defend. Not wanting to have it used for stuff but holy stuff." +"Uth'Doon" -> "It's high steel called. Only lil' lil' ones know how make." +"Zatragil" -> "Most ancients use dream silver for different stuff. Now ancients most gone. Most not know about." + +"Teshial" -> "Is one of elven family or such thing. Me not understand lil' ones and their busisness." +"Deraisim" -> * +"Cenath" -> * +"Kuridai" -> * + +"cyclops" -> "Me people not live here much. Most are far away." +"excalibug" -> "Me wish I could make weapon like it." + +"fire","sword" -> "Do lil' one want to trade a fire sword?", topic=1 +"bright","word" -> "Do lil' one want to trade a bright sword?", topic=1 +"warlord","sword" -> "Do lil' one want to trade a warlord sword?", topic=1 +"sword","of","valor" -> "Do lil' one want to trade a sword of valor?", topic=1 +"serpent","sword" -> "Do lil' one want to trade a serpent sword?", topic=1 +"enchanted","plate" -> "Do lil' one want to trade an enchanted plate armor?", topic=1 +"dragon","shield" -> "Do lil' one want to trade a dragon shield?", topic=1 + +Topic=1,"yes" -> "You not have stuff me want for." +Topic=1 -> "Silly lil' one you are." +} diff --git a/data/npc/billy.npc b/data/npc/billy.npc new file mode 100644 index 0000000..cabd499 --- /dev/null +++ b/data/npc/billy.npc @@ -0,0 +1,98 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# billy.npc: Datenbank fuer den Farmer Billy + +Name = "Billy" +Outfit = (128,58-63-58-115) +Home = [32037,32205,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Howdy %N." +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "You did not pay your tax. Get lost!", Idle +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Can't you see i am talking? Wait!", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "You did not pay your tax. Get lost!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "YOU RUDE $§&$" + +"bye" -> "Bye.", Idle +"farewell" -> "Farewell.", Idle +"how","are","you" -> "I think, I'm fine." +"job" -> "I am farmer and a cook." +"cook" -> "I am the best cook around. You can sell me most types of food." +"willie" -> "Don't listen to that old wannabe, I'm the best cook around." +"recipe" -> "I would love to try a pancake. But I lack a decent pan. If you get me one, I will reward you." +"name" -> "Billy." +"time" -> "I came here to have some peace and leisure so leave me alone with 'time'." +"help" -> "Can't help you, sorry. I'm a cook, not a priest." +"monster" -> "Don't be afraid, in the town you should be save." +"dungeon" -> "You'll find a lot of dungeons if you look around." +"sewer" -> "The local sewers are invested by rats, fresh rats give a good stew, you can sell them to me." +"god" -> "I am the god of cooking, indeed!" +"king" -> "The king and his tax collectors are far away. You'll meet them soon enough." +"obi" -> "I like him, we usualy have a drink or two once a week and share storys about Willie." +"seymour" -> "I don't like his headmaster behaviour. Then again, he IS a headmaster after all." +"dallheim" -> "One of the kings best men, here to protect us." +"cipfried" -> "He never leaves this temple and only has time to care about those new arivals." +"amber" -> "Shes pretty indeed! I wonder if she likes bearded men." +"weapon" -> "Ask one of the shopkeepers. They make a fortune here with all those wannabe heroes." +"magic" -> "I can spell but know no spell." +"spell" -> * +"tibia" -> "There is so much to be explored! Better hurry to get to the continent!" + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +"sell","bread" -> Type=3600, Amount=1, Price=1, "So, you want to sell a bread? Hmm, I give you %P gold, ok?", Topic=2 +"sell","cheese" -> Type=3607, Amount=1, Price=2, "So, you want to sell a cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","salmon" -> Type=3579, Amount=1, Price=2, "So, you want to sell a salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with this stinking &*#@@!" +"sell","cherry" -> Type=3590, Amount=1, Price=1, "So, you want to sell a cherry? Hmm, I give you %P gold, ok?", Topic=2 + +"sell",%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=1*%1, "So, you want to sell %A breads? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=2*%1, "So, you want to sell %A cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A hams? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=2*%1, "So, you want to sell %A salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"fish" -> "Go away with this stinking &*#@@!" +"sell",%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "So, you want to sell %A cherries? Hmm, I give you %P gold, ok?", Topic=2 + +"rat" -> "So you bring me a fresh rat for my famous stew?", Type=3994, Amount=1, Price=2, Topic=2 +"sell","rat" -> "So you bring me a fresh rat for my famous stew?", Type=3994, Amount=1, Price=2, Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "So you bring me %A fresh rats for my famous stew?", Topic=2 + +"sell" -> "I sell various kinds of food." +"buy" -> "I buy food of most kind. Since I am a great cook I need much of it." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2,"no" -> "Then not." + +"pan" -> Type=3466, Amount=1, "Have you found a pan for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "A pan! At last! Take this in case you eat something my cousin has cooked.", Delete(Type),Amount=1, Data=1, Create(3153) +Topic=3,"yes" -> "Hey! You don't have it!" +Topic=3,"no" -> "$&*@!" +Topic=3 -> * +} diff --git a/data/npc/blindorc.npc b/data/npc/blindorc.npc new file mode 100644 index 0000000..d11239d --- /dev/null +++ b/data/npc/blindorc.npc @@ -0,0 +1,44 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blindorc.npc: Datenbank fuer den blinden Orkschmied + +Name = "Blind Orc" +Outfit = (5,0-0-0-0) +Home = [32102,32130,4] +Radius = 1 + +Behaviour = { +ADDRESS,"charach",! -> "Ikem Charach maruk." +ADDRESS,! -> "Buta humak!", Idle +BUSY,"charach",! -> "Ikem napak aluk." +BUSY,! -> NOP +VANISH,! -> "Futchi." + +"futchi" -> "Futchi!", Idle +"ikem","goshak" -> "Ikem pashak porak, bata, dora. Ba goshak maruk?" + +# verkauft SABRE, SHORT SWORD, SWORD, HATCHET +"goshak","porak" -> "Ikem pashak charcha, burka, burka bata, hakhak. Ba goshak maruk?" +"goshak","charcha" -> Type=3273, Amount=1, Price=25, "Maruk goshak ta?", Topic=1 +"goshak","burka" -> Type=3294, Amount=1, Price=30, "Maruk goshak ta?", Topic=1 +"goshak","burka","bata" -> Type=3264, Amount=1, Price=85, "Maruk goshak ta?", Topic=1 +"goshak","hakhak" -> Type=3276, Amount=1, Price=85, "Maruk goshak ta?", Topic=1 + +# verkauft LEATHER ARMOR, STUDDED ARMOR, STUDDED HELMET +"goshak","bata" -> "Ikem pashak aka bora, tulak bora, grofa. Ba goshak maruk?" +"goshak","bora" -> Type=3361, Amount=1, Price=25, "Maruk goshak ta?", Topic=1 +"goshak","tulak","bora" -> Type=3378, Amount=1, Price=90, "Maruk goshak ta?", Topic=1 +"goshak","grofa" -> Type=3376, Amount=1, Price=60, "Maruk goshak ta?", Topic=1 + +# verkauft BRASS SHIELD +"goshak","dora" -> "Ikem pashak donga. Ba goshak maruk?" +"goshak","donga" -> Type=3411, Amount=1, Price=65, "Maruk goshak ta?", Topic=1 + +# verkauft BOGEN, PFEILE +"goshak","batuk" -> Type=3350, Amount=1, Price=400, "Ahhhh, maruk goshak batuk?", Topic=1 +"goshak","pixo" -> Type=3447, Amount=10,Price=30, "Maruk goshak tefar pixo ul batuk?", Topic=1 + +Topic=1,"mok",CountMoney>=Price -> "Maruk rambo zambo!", DeleteMoney, Create(Type) +Topic=1,"mok" -> "Maruk nixda!" +Topic=1,"burp" -> "Buta maruk klamuk!" +Topic=1 -> * +} diff --git a/data/npc/blindprophet.npc b/data/npc/blindprophet.npc new file mode 100644 index 0000000..0ebd9e4 --- /dev/null +++ b/data/npc/blindprophet.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blindprophet.npc: Datenbank für den blinden affenpropheten + +Name = "The Blind Prophet" +Outfit = (117,0-0-0-0) +Home = [33022,32604,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",QuestValue(293)>14,! -> "Be greeted, friend of the apes." +ADDRESS,"hi$",QuestValue(293)>14,! -> * + +ADDRESS,"hello$",! -> "You not should be here! You go! You go!", Idle +ADDRESS,"hi$",! -> * + +ADDRESS,"hello$",! -> "Be greeted, friend of the apes." +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Sorry, I am busy." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"name" -> "Me put name away name long ago. Now only blind prophet of ape people are." +"job" -> "Me prophet and guardian is." +"prophet" -> "Me is who in dreams speak to holy banana. Me divine the will of banana." +"guardian" -> "Me guard the forbidden land behind the great palisade. If any want to enter, he must ask me for transport." +"forbidden" -> * + +"transport",QuestValue(293)>14 -> "You want me to transport you to forbidden land?", topic=1 +"transport",QuestValue(293)<15 -> "No!" +"yes",topic=1,PZBlock,! -> "Anger of battle is burning in you! First calm down." +"yes",topic=1 -> "Take care!", Teleport(33026,32580,6), EffectOpp(11) +"no" ,topic=1 -> "Wise decision maybe." + +"Hairycles" -> "Good ape he is. Has to work hard to make other apes listen but you helped a lot." +"excalibug" -> "Me not know. Me seldom have visions of not banana related objects." +"bong" -> "Our holy ancestor he is. Big as mountain. Lizards say they built palisade to keep him but we not believe ...", "We think Bong palisade built to have peace from pesky lizards. We respect peace of Bong, keep people away from forbidden land." +"ape" -> "Our people a lot to learn have. One day we might live in peace with you hairless apes, who knows." +"port", "hope" -> "Hairless apes strange people are. " +"lizard" -> "The lizards evil and vengeful are. Ape people on guard must be." +"hair" -> "Me visions show hair in the far north west of forbidden land. Near coast look for signs of Bongs presence." +} diff --git a/data/npc/blood.npc b/data/npc/blood.npc new file mode 100644 index 0000000..b749fe4 --- /dev/null +++ b/data/npc/blood.npc @@ -0,0 +1,83 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blood.npc: Datenbank für General Harkath Bloodblade + +Name = "Harkath Bloodblade" +Outfit = (131,76-38-38-76) +Home = [32342,32184,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$","general",! -> "Salutations, commoner %N!" +ADDRESS,"salutations$","general",! -> "Salutations, commoner %N!!" +ADDRESS,"hello$",! -> "Address me properly %N!", Idle +ADDRESS,"hi$",! -> "Address me properly %N!", Idle +ADDRESS,"hail$",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,"salutations$",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE! I am busy!" +BUSY,"hi$",! -> "SILENCE! I am busy!" +BUSY,"hail$",! -> "SILENCE! I am busy!" +BUSY,"salutations$",! -> "SILENCE! I am busy!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "No news are good news." +"king" -> "HAIL TO KING TIBIANUS, OUR WISE LEADER!" +"leader" -> "King Tibianus III is our wise and just leader." +"job",female -> "My Lady, I am the general of the king's army." +"job",male -> "I am the general of the king's army." +"how","are","you"-> "I am in perfect condition, commoner." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects our city. I divided it into three battlegroups." +"guard" -> * +"general" -> "It is my duty to lead the armed forces of our beloved city into battle against our enemies." +"enemies" -> "Evil has many faces. The servants of evil cannot always be recognized as easily as Ferumbras, for instance." +"enemy" -> * +"battlegroup" -> "The battlegroups are the 'dogs of war', the 'red guards', and the 'silver guards'." +"castle" -> "The castle is prepared to withstand any direct assault." +"subject" -> "We all live under the rule of our beloved king." +"dogs","of","war"-> "They are our main army." +"red","guard" -> "They are our special forces. Some serve as city guards, others as secret police." +"secret","police"-> "The branch of the red guard that serves as secret police is known as the TBI." +"tbi$" -> "The Tibian Bureau of Investigation. Kind of secret police. I don't bother much about such things, I prefer my fights eye to eye." +"chester" -> "I don't know much about him. He is a very secretive person." +"silver","guard" -> "The best sorcerers, paladins, knights, and druids of our forces are chosen to serve as silver guards. They are the bodyguards of the king." +"city" -> "The rapid growth of the city makes it hard to patrol and vulnerable to attacks." +"scum" -> "We will eliminate all resistance against law and order!" +"stutch" -> "He is one of our best men and serves in the silver guard." +"harsky" -> * +"bozo" -> "I hardly know him." +"sam" -> "Sam is responsible to supply our troops with weapons and armor." +"weapon" -> * +"armor" -> * +"elane" -> "AH! WHAT A WOMAN!" +"gorn" -> "He was an adventurer once. He was a fine fighter but lacked the discipline to serve in our army." +"benjamin" -> "He was the king's general before I was promoted. Poor guy, lost his mind in a battle against the evil Ferumbras." +"ferumbras" -> "He is allied with evil itself! Each time we kill him he returns to take revenge." +"join" -> "Join what?" +"join","army" -> "Sorry, we don't recruit today. Perhaps you can join by doing a quest for the king." +"quest" -> "Sometimes the king calls for heroes. Keep eyes and ears open! I also heared Baxter has some work for young adventurers." +"mission" -> * +"god" -> "I whorship Banor, the first warrior!" +"banor" -> "He is the idol of all knights and paladins." +"zathroth" -> "Don't mention the dark one!" +"brog" -> "The orcs, trolls, and cyclopses sacrificed more than one of my men to Brog, the raging one." +"monster" -> "They seldom dare to attack the city itself." +"excalibug" -> "In the legends it is told, that this weapon made its wielder able to fight the mightiest demons hand to hand." +"rebellion" -> "Ask Chester of the T.B.I. about that." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +Topic=2 -> "You should be careful with your words!" +} diff --git a/data/npc/blossom.npc b/data/npc/blossom.npc new file mode 100644 index 0000000..95568e2 --- /dev/null +++ b/data/npc/blossom.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# blossom.npc: Datenbank für die Wächterin Blossom Bonecrusher + +Name = "Blossom Bonecrusher" +Outfit = (139,96-19-63-95) +Home = [32390,31787,7] +Radius = 3 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/data/npc/bonifacius.npc b/data/npc/bonifacius.npc new file mode 100644 index 0000000..cd1dfc3 --- /dev/null +++ b/data/npc/bonifacius.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bonifacius.npc: Datenbank für den Lebensmittelhändler Bonifacius + +Name = "Bonifacius" +Outfit = (128,59-82-58-95) +Home = [33165,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Thousands greetings, %N. How may I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am deeply sorry, I am busy right now. I'll tell you when I'm done %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods bless your travels." + +"bye" -> "May the gods bless your travels.", Idle +"name" -> "My name is Bonifacius." +"job" -> "I sell delicious food. May I be at your service?" +"time" -> "It is %T right now." +"king" -> "Our wise king, Tibianus, be praised!" +"tibianus" -> * +"army" -> "I am glad about their healthy appetite." +"ferumbras" -> "Is that a new, exotic vegetable?" +"excalibug" -> "Uh, I hate bugs of all kind." +"thais" -> "We recive food from thais with every arriving ship." +"tibia" -> "The world provides us with all kinds of delicious food." +"carlin" -> "We do not buy any wares there. Our food is of high quality, Thaian origin." +"edron" -> "Our climate is quite rough, so we can only grow wheat here, but no fruits." +"news" -> "I heard the corn prices in Thais are going to be increased." +"rumors" -> * + +"buy" -> "I can offer you meat, salmons, fruits, cookies, rolls, eggs, and cheese." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, pumpkins and melons. What do you want?" + +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=4, "Do you want to buy a salmon for %P gold?", Topic=1 +"orange" -> Type=3586, Amount=1, Price=5, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you want to buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you want to buy a roll for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=4*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=5*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you want to buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, I'm sorry, but I can't give you credit." +Topic=1 -> "Don't you like my wares?." +} diff --git a/data/npc/boozer.npc b/data/npc/boozer.npc new file mode 100644 index 0000000..974ae97 --- /dev/null +++ b/data/npc/boozer.npc @@ -0,0 +1,72 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# boozer.npc: Datenbank für den Wirt Boozer + +Name = "Boozer" +Outfit = (128,76-20-116-76) +Home = [32921,32068,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Hard Rock Racing Track, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "You'll be back." + +"bye" -> "You'll be back.", Idle +"job" -> "I am the bartender here at the racing track." +"tavern" -> * +"frodo" -> "I heard about his tiny tavern in Thais." +"name" -> "Just call me Boozer. Everyone does that." +"time",male -> "No clue, boy." +"time",female -> "No clue, girl." +"king" -> "The king is far away, so who cares?" +"tibianus" -> * +"army" -> "Good customers." +"ferumbras" -> "Guess he'd be bad news for business." +"excalibug" -> "Heard about it now and then. Then again I also hear there a bogeyman somewhere in the swamps." +"bogeyman" -> "Just a tale to scare the kids." +"thais" -> "If you like that Thais that much just go there." +"tibia" -> "People from all over Tibia come here to buy, sell, gamble, and get drunk until they puke." +"carlin" -> "Heard about that women there. Must visit that wenches someday." +"amazon" -> "I guess they just have not met the right man yet." +"news" -> "The swampelves, down at Shadowthorn, are up to some trouble again." +"rumors" -> * +"swampelves" -> "Some elves gone evil so to say. They now live in a small village to the south called Shadowthorn. No big deal. Who cares about some carrot-eating musicians at all?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here is what you ordered.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have the gold. If we were gambling I'd call you a cheater ... and you know what happens to cheaters, don't you?" +Topic=1 -> "Then not, fine with me." + + +"buy" -> "I can offer you food and drinks. Get anything else somewhere else and don't bother me." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "So you are looking for food? We have cookies, bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +} diff --git a/data/npc/boques.npc b/data/npc/boques.npc new file mode 100644 index 0000000..6916548 --- /dev/null +++ b/data/npc/boques.npc @@ -0,0 +1,96 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# boques.npc: Datenbank für den Djinnkoch Bo'ques + +Name = "Bo'ques" +Outfit = (80,0-0-0-0) +Home = [33101,32520,5] +Radius = 2 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Hey! A human! What are you doing in my kitchen, %N?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Whoa. Do I look as if I had two heads? Only one at a time, %N!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Now, where was I?" + +"bye" -> "Goodbye. I am sure you will come back for more. They all do.", Idle +"farewell" -> * + +"name" -> "My name is Bo'ques. Perhaps you know my name from a restaurant guide." +"bo'ques" -> "You want Bo'ques? Well, you have found him, I'd say." +"job" -> "I'm preparing the food for all djinn in Ashta'daramai. ...", + "Therefore I'm what is commonly called a cook, although I do not like that word too much. It is vulgar. I prefer to call myself 'chef'." +"cook" -> * +"chef" -> "Chef sounds nice, doesn't it? Well... I must admit I do not really know what it means, but it certainly sounds classy." +"food" -> "I know many recipes for preparing the finest food on Darama and maybe even whole Tibia!" + +"king" -> "Gabel used to be king, you know. I must confess I miss those days a bit because I was allowed to carry the title of his royal majesty's personal cook. Ah, those were the days." +"gabel" -> "He is my boss. A most loyal customer and a real con... conni... well, a man of taste, at any rate. His favourite dish is Scarabée au Vin served with onions and rice." +"connoisseur" -> "Yes! That's it! I have always trouble with pronouncing that damn word. A conno... conni... ah, hang it all!" +"djinn" -> "That is our race. It has seen better days, you know. ...", + "It would have been better for us all if more djinn would share my interest in cooking. But no! Bashing each others' heads in is the only thing they are good at! Vandals and trogo ...trogli ... and cavemen, that's what they are!" +"efreet" -> "A bunch of ignorants and primitives, that's what they are. You should see the things they eat! ...", + "You know they serve ketchup with just about every kind of meal! Ketchup! Oh, those barbarians." +"marid" -> "That is us - the loyalists who have have remained faithful to Gabel and to good cooking." +"malor" -> "That accursed traitor! I think there will never be peace until he is completely vanquished. If only he would allow me to cook for him. I would fix him a dinner he would never forget." +"mal'ouquah" -> "Ah yes. The efreets' notorious fortress. I have never been there. That is no place for an artist such as myself." +"ashta'daramai" -> "That is our little fortress - our home. Nice, isn't it? I find it inspirational, although I find the culinary facilities could do with some improvements." +"human" -> "I totally agree with Gabel that djinn and humans can learn from each other. ...", + "Take cooking, for example. It has such a long tradition among humans - even I could still learn a thing or two from the famous cooks at king Tibianus' court!" +"zathroth" -> "That is a sad story, and like most djinn I dislike talking about it. Let's put it this way. Once there was a great cook who worked hard to prepare the finest meal of his life. ...", + "But when he found that the product of his efforts did not meet his expectations he just ditched it even though it was wonderfully unique in its own special way. ...", + "You know what I think? I think Zathroth was a bad cook." +"tibia" -> "It may be that this world is wide and full of adventure, but to be honest I am not at all keen to see it myself. A comfortable lamp to sleep in and a well equipped kitchen is all I need." +"daraman" -> "Ah yes. That human WAS special, believe me. Did you know I talked to him myself, back in those days? In fact, I even had an argument with him because he dared to insult my work! He drove me mad when he called me a self-indulgent glutton. ...", + "But you know, eventually we came to respect each other! He taught me to stress quality rather than quantity, and he came to appreciate my 'Chili con Cobra'. Today I know that having met him was a major step forward in my development as a culinary artist." +"darashia" -> "I have heard good things about this place. I understand the Caliph is a true gourmet. People who eat good food can't be bad, that's what I say." +"scarab" -> "Ah yes. I like them well. Especially with a good sauce or in a stew. But they have to be young! Have you tried ancient scarab? Their meat is impossible to chew unless you have teeth made of titanium." +"edron" -> "Ah, the northern cities. One day I will start an extensive culinary expedition there. I have this dream of writing some sort of culinary guide, you know. Isn't that a great idea?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "No djinn who is in his right state of mind would want to go there? What for? The land is ruled by an undead nut case, and from what I have heard his subjects are no better." +"pharaoh" -> "Apparently he is an undead! And what's worse, he actually chose that fate for himself! Undead! Imagine that! Never sleep, never laugh, and worst of all: Never eat! What a crackpot!" +"palace" -> "Who would like to live in a palace if there is never the delicious smell of freshly prepared food! I would not want to live there. Not for love nor for money." +"ascension" -> "As far as I know that is one of the pharaoh's crazy ideas. Just a load of baloney." +"rah" -> "Hm. Is that some exotic spice? Hang on, I know! It is a kind of lizard stew - right?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "These mountains are a nice place to live in, but food-wise they are pretty lousy. We basically import everything we eat from the lowlands, trading them for magic trinkets and for gold. ...", + "The only plants that grow well in these mountains are potatoes, and they are not really my idea of Haute Cuisine." +"kha'labal" -> "Such a shame about that land. It wasn't always a desert, you know. That land was garden, a veritable paradise. Just the thought of the fruit that used to grow there makes my mouth water . Well, guess who messed it up." +"war" -> "I have never been much of a warrior, but I will storm into battle swinging my meat cleaver if necessary. We simply must win this war!" +"melchior" -> "Ah yes, the trader - right? I remember him. He used to travel the mountains with his mule. A tough haggler and a real skinflint, he was. I thought he had fallen down a cliff with all his money." +"alesar" -> "Ah - that guy. You probably don't know it, but nobody around here likes to hear that name. It brings back painful memories, you know. His betrayal was such a heavy blow to us. I think I will never understand what made him do it? It is a mystery." +"lamp" -> "You would not believe it, but those lamps are actually quite comfy. And on top of that they are immensely practical! Did you ever try to stash one of your beds into your pocket?" +"fa'hradin" -> "That djinn is so engrossed in his work! I constantly have to remind him to eat because if I didn't he would simply forget. Forgetting to eat! Can you imagine that?" +"djema" -> "Djema is a nice girl, but she eats so little. It's frustrating, really. Humans and their little stomachs!" + + +"recipe",QuestValue(280)=0 -> "My collection of recipes is almost complete. There are only but a few that are missing. ...", + "Hmmm... now that we talk about it. There is something you could help me with. Are you interested?", Topic=2 +"mission",QuestValue(280)=0 -> * +Topic=2,"yes" -> "Fine! Even though I know so many recipes, I'm looking for the description of some dwarven meals. ...", + "So, if you could bring me a cookbook of the dwarven kitchen I will reward you well.", SetQuestValue (280, 1) +Topic=2 -> "Well, too bad." + +"book",QuestValue(280)=1 -> "Do you have the cookbook of the dwarven kitchen with you? Can I have it?", Topic=1 +"cookbook",QuestValue(280)=1 -> * +"book",QuestValue(280)=2 -> "Thanks again, for bringing me that book!" +"cookbook",QuestValue(280)=2 -> * + +Topic=1,"yes",Count(3234)=1,! -> "The book! You have it! Let me see! ...", + "Dragon Egg Omelette, Dwarven beer sauce... it's all there. This is great! Here is your well-deserved reward. ...", + "Incidentally, I have talked to Fa'hradin about you during dinner. I think he might have some work for you. Why don't you talk to him about it?", Amount=1, Delete(3234), Amount=3, Create(3029), SetQuestValue(280,2) +Topic=1 -> "Too bad. I must have this book." +} diff --git a/data/npc/borkas.npc b/data/npc/borkas.npc new file mode 100644 index 0000000..1091228 --- /dev/null +++ b/data/npc/borkas.npc @@ -0,0 +1,39 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# borkas.npc: Möbelverkäufer Borkas in Venore + +Name = "Borkas" +Outfit = (128,77-43-38-76) +Home = [32992,32068,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hey %N, what'cha want?" +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, %N. Would ya mind not interruptin'? Thanks.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, sod off..." + +"bye" -> "Thanks and see ya.", Idle +"farewell" -> * +"job" -> "I'm into sellin' furniture. My grandfather was in that business, then my father, and so am I." +"shop" -> * +"name" -> "I'm Borkas Flersson, but let's not waste precious tradin' time with smalltalk." +"time" -> "Time is %T now." +"thanks" -> "No prob." +"thank","you" -> * +"allen" -> "Hes my boss but he likes to be one of us and sells some of his wares personally." +"richardson" -> * + +"offer" -> "I'm selling containers here." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/bozo.npc b/data/npc/bozo.npc new file mode 100644 index 0000000..c788fae --- /dev/null +++ b/data/npc/bozo.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bozo.npc: Datenbank für den Hofnarren Bozo + +Name = "Bozo" +Outfit = (128,86-93-82-79) +Home = [32313,32183,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",female,! -> "Hello, hello, hello, little lady %N!" +ADDRESS,"hi$",female,! -> "Hello, hello, hello, little lady %N!" +ADDRESS,"hello$",male,! -> "Hi there, how's it hanging, %N!" +ADDRESS,"hi$",male,! -> "Hi there, how's it hanging, %N!" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait and listen to my jokes, I am sooooo funny!" +BUSY,"hi$",! -> "Wait and listen to my jokes, I am sooooo funny!" +BUSY,! -> NOP +VANISH,male,! -> "How did he do that??" +VANISH,! -> "Women! They all do that to me!" + +"bye" -> "Remember: A joke a day keeps the ghouls away!", Idle +"farewell" -> * +"job" -> Price=50, "I am the royal jes ... uhm ... the royal tax-collector! Do you want to pay your taxes?", Topic=1 +"news" -> "I know the newest jokes in tibia." +"how","are","you"-> "Thank you, I'm fine, the gods are with me." +"sell" -> "Sell? Hmm, I know a little about magic and by chance I can sell you a truly unusual weapon." +"durin" -> "Isn't he the author of the book 'fun with demons'?" +"stephan" -> "He is kind of a father figure to me. Of course he denies all kinship to me." +"steve" -> "He's a smart one. I heared he hid in a foreign country as the first bugs showed up." +"name" -> "My name is Bozo. But it's more than a name, it's a lifestyle to me!" +"time" -> "Since you met me it is happy hour for you." +"help" -> "I am a jester, not a doctor!" +"jester" -> "Do you wish to join the fools' guild?", Topic=6 +"fool" -> "Do you wish to join the fools' guild?", Topic=6 +"joke" -> "I know some 'monstrous' jokes!" +"idiot" -> "To me it's just a profession, but for you it's a state of mind!" +"wish" -> "If you have a wish to CIP just write a letter and place it in a dustbin of your choice." +"excalibug" -> "I am not foolish enough to believe in the existence of this weapon." +"wallcarving" -> "Oh, I saw some demoncarvings in the dungeons as I hid there after a little joke on old Stutch." +"demoncarving" -> "Yes, they showed demons, seven actually, dancing around a sword! In a flaming pit of some kind." +"flaming","pit" -> "Ah, don't ask me! Usually mages and mystics know more about such stuff." + +"monster" -> "I know a lot of monster jokes. Just tell me a monster's name, come on." +"demon" -> "Why are the experienced heroes quicker than others? ... The demons love fast food!" +"ghoul" -> "Where do the ghouls buy their robes? ... In a Boooohtique!" +"dragon" -> "Why do dragons breathe fire? ... They ate too many sorcerers in chili sauce!" +"skeleton" -> "Why do skeletons flee if wounded? ... They are so spineless!" +"orc" -> "Why do orcs have green skin? ... They ate at Frodo's!" +"cyclops" -> "How many eyes does a cyclops have? ... One for each IQ point of their opponents!" +"beholder" -> "Why are beholders so ugly? ... Because their mom and dad were beholders, too!" +"rat" -> "Why does the rat have a wooden leg? ... Because it is a former pirate!" +"spider" -> "Why did the spider cross the road? ... Because it ... oh you already know this one!?" +"troll" -> "Why do trolls live underground? ... Because on the ground there are so many PKs!" +"wolf" -> "Why do the wolves howl? ... Hey, if you're online that long you can't help but behave that way!" +"mino" -> "What do all little minotaurs want to become when they are grown-ups? ... Cowboys, of course!" +"dungeon" -> "If you are a bad jester you get a chance to visit them now and then." +"sewer" -> "Good place for picking up apples and women." +"oswald" -> "If you believe half the rumours he's spreading, you are going to get in a lot of trouble." +"update" -> "Hey! I am supposed to make the jokes here!" +"god" -> "I better make no jokes about THIS matter." +"king" -> "Nah, no jests about His Royal Highness." +"sam" -> "Did you know that he now sells a 'power axe of doom'? Run and buy it, he has only three in store." +"benjamin" -> "He would make a fine jester, too." +"gorn" -> "He sells spell scrolls each day at midnight, but you have to address him that very second." +"quentin" -> "He's my baby brother. If you tell him I sent you, he will grant you an extra spell or two." +"bozo" -> "Thats me: Bozo, the jester!" +"weapon" -> Type=3473, Amount=1, Price=250, "Do you want to buy a 'mace of the fury' for 250 gold?", Topic=3 +"magic" -> Price=200, "I actually know some spells! Do you want to learn how to 'lessen your load' for %P gold?", Topic=2 +"spell" -> Price=200, "I actually know some spells! Do you want to learn how to 'lessen your load' for %P gold?", Topic=2 +"tibia" -> "I rarely leave the castle. It's a real stress to be popular like me." +"castle" -> "The castle is my home. A place fit for a jester and all other fools. Feel welcome." +"muriel" -> "Better don't mess with sorcerers!" +"elane" -> "She's pretty but has a kind of too burning affection for my taste." +"marvik" -> "Humourless old guy! Once turned me into a frog for painting his distasteful cave in pink." +"gregor" -> "A man of steel, with a stomach of wax. Never offer him a beer!" +"paladin",Paladin-> "I wanted to become a paladin, too, but I was overqualified!" +"paladin" -> "They are the king's favourites, because they know how to 'bow'." +"sorcerer",Sorcerer-> "I wanted to become a sorcerer, too, but I was overqualified!" +"sorcerer" -> "The good thing about them is that they can't be at two places at the same time." +"druid",Druid -> "I wanted to become a Druid, too, but I was overqualified!" +"druid" -> "If you are in Druidville, do as the rabbits do." +"knight",Knight -> "I wanted to become a knight, too, but I was overqualified!" +"knight" -> "Did you notice that old knights have their scars just on their backs?" +"noodles" -> "Hey, the little one is almost as funny as me!" +"dog" -> "Are we talking about Noodles?" +"poodle" -> "Are we talking about Noodles?" +"guild" -> "Since the first guild showed up there's a great demand for jesters and fools to join them." +"necromant","nectar" -> "Peeew! That sounds disgusting! Are you a cook at Frodo's?" +"necromant" -> "Don't feed the necromants." +"lady",male -> "Well, you don't behave ladylike just because you dress like one!" +"lady",female -> "Has any man said to you that you're not only beautiful but also intelligent?", Topic=5 +"kiss",male -> "Uh, go away!", Idle +"kiss",female -> "Do you want to kiss me?", Topic=4 +"hugo" -> "I had a cousin named like that." +"cousin" -> "He died some years ago." + +Topic=1,"yes",CountMoney>=Price -> "Thank you very much. I will have a drink or two on your health!", DeleteMoney +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Well, perhaps later." +Topic=2,"yes",CountMoney>=Price -> "Here you are, I already lessened your load.", DeleteMoney +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "You don't know what offer you have passed!" +Topic=3,"yes",CountMoney>=Price -> "And here it is, it suits you well!", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "You dont know what offer you have passed!" +Topic=4,"yes" -> "Uh, oh! ... I am seeing stars!", EffectMe(13) +Topic=4 -> "Pah, I didn't want to kiss you anyway!" +Topic=5,"yes" -> "This is a world of fantasy and full of surprises!" +Topic=5 -> "Well, think about it!" +Topic=6,"yes" -> "Sorry, you already are a member." +Topic=6 -> "Well, you are already a member anyway." +} diff --git a/data/npc/brasith.npc b/data/npc/brasith.npc new file mode 100644 index 0000000..02637c0 --- /dev/null +++ b/data/npc/brasith.npc @@ -0,0 +1,77 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brasith.npc: Datenbank für den Obsthändler Brasith + +Name = "Brasith" +Outfit = (144,60-94-58-76) +Home = [32692,31589,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "I am Brasith Seedsinger." +"job" -> "You may buy all the things we grow or gather at this place." +"time" -> "Sorry, I can't help you." + +"carlin" -> "The humans of Carlin at least ry to live in harmony with nature." +"thais" -> "I heared only terrible storys about that city." +"venore" -> "Their traders seem suspiciously freindly. I don't trust them." +"roderick" -> "His house is an impurity in our city in unity with nature." +"olrik" -> "This poor humans seems to think he might become one of us by spendig time with us." + +"elf" -> "Our race lacks unity, which is a very sad thing. And the differences we have will grow and grow until eventually there is no race left." +"elves" -> * +"dwarf" -> "They work the earth and claim knowledge about it, but they know only about minerals, not about the life it stands for." +"human" -> "They are so many, so planless, so divided. They have choosen a path I do not want for my own race" +"troll" -> "I don't claim to understand this creatures but sometimes they are more close to the roots than we are." +"cenath" -> "The Cenath forgot as many as they learned. I doubt they find the wisdom they are looking for without the things they neglected in their pursuit of knowledge." +"kuridai" -> "The Kuridai left the true path and can't see their error. Their way of living may have been suitable in the past, but if they don't come back to us, their path will lead into darkness." +"deraisim" -> "We have still much to learn but we are on the correct path at least." +"abdaisim" -> "The Abdaisim are true to the ways of our race, maybe even more close than we. But by abandoning the other elves they harm themselves more than they know." +"teshial" -> "They are lost, and if they still exist they are alone in the cold and the darkness." +"ferumbras" -> "He thinks that he is incredibly powerful, but his is only the mindless power of destruction." +"crunor" -> "We abandoned the gods a long time ago. A short time after they abandoned us." +"plant" -> "Life takes many forms. Plants are a very basic form of life. Its simplicity makes them close to the core of nature." +"tree" -> * +"forest" -> "The beauty of a forest is something easy to be missed by the unobservant." +"field" -> "With the growth of a community comes the need to 'use' nature rather then to 'flow' with nature. This is sad but necessary." + +"offer" -> "I sell corncobs, cherries, grapes, melons, pumpkins, bananas, strawberries, and carrots." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> * + +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"strawberry" -> Type=3591, Amount=1, Price=1, "Do you want to buy a strawberry for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"strawberries" -> Type=3591, Amount=%1, Price=1*%1, "Do you want to buy %A strawberries for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + +"bugmilk" -> Type=2875, Data=9, Amount=1, Price=15, "Do you want to buy a bottle of bugmilk for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/data/npc/brengus.npc b/data/npc/brengus.npc new file mode 100644 index 0000000..220953d --- /dev/null +++ b/data/npc/brengus.npc @@ -0,0 +1,201 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Brengus.npc: Datenbank für den waffen und rüstungshändler Brengus + +Name = "Brengus" +Outfit = (132,79-57-57-95) +Home = [32634,32747,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait, I am busy right now", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a tradesman. I sell and buy weapons and armor." +"name" -> "My name is Brengus." +"time" -> "Sorry, my watch didn't take the moist air here too well." +"king" -> "This is the king's land. It was a wise decision to have us people from Venore rule this settlement." +"venore" -> "I miss my home like most of us here, but I have duties and responsibilities. After all, there is some meagre profit to earn here." +"thais" -> "A nice big city of course, but it lacks style and grandeur. Such qualities you will only find when you visit my hometown Venore." +"carlin" -> "I hope the king will take these rebelling women soon under Thaian guidance once again. I hate to see the profits wasted that could be earned there." +"edron" -> "A rich and lovely island. Sadly those knights kept our tradesmen out of business for some unknown reason. I am convinced after seeing our success with this colony here, the king will allow Venore to become more present over there too." +"jungle" -> "Of course there are problems. But problems are there to keep those out of business who are not prepared and diligent enough." + +"tibia" -> "It's a world full of possibilities." + +"kazordoon" -> "The dwarves of Kazordoon are stubborn people and it's hard to have dealings with them. But as often, the hardship is very rewarding for those who are able to handle them." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "It's complicated to negotiate with those elves but it is possible." +"elves" -> * +"elfs" -> * +"darama" -> "We have hardly scratched the surface of all the possibilities to gain profit that are hidden on this continent." +"darashia" -> "The sandwasp's honey is quite useful. But that's the only noteworthy thing about this unimportant desert hicktown." +"ankrahmun" -> "It's somewhat hard to evaluate if this city poses another threat or a new market. Only time can tell." +"ferumbras" -> "He is bad for business. The big trading houses of Venore have yet to decide what price they will put on his head." +"excalibug" -> "If you ever stumble upon that interesting piece of jewellery, contact me. I know somebody who would pay a decent amount of crystal to add it to his collection of curiosities." +"apes" -> "They are neither skilled in a craft nor do they know about the concept of trade. They constantly raid our colony to steal items." +"lizzard" -> "The lizzard folk is hostile to us but luckily they live far enough from here to be an immediate danger." +"dworcs" -> "They should be driven into the sea." + + + + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain and brass armors. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy some?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell a spear for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","templar","scytheblade" -> Type=3345, Amount=1, Price=200, "Do you want to sell a templar scytheblade for %P gold?", Topic=2 +"sell","ripper","lance" -> Type=3346, Amount=1, Price=500, "Do you want to sell a ripper lance for %P gold?", Topic=2 +"sell","hunting","spear" -> Type=3347, Amount=1, Price=250, "Do you want to sell a hunting spear for %P gold?", Topic=2 +"sell","banana","staff" -> Type=3348, Amount=1, Price=1000, "Do you want to sell a banana staff for %P gold?", Topic=2 + + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","tusk","shield" -> Type=3443, Amount=1, Price=850, "Do you want to sell a tusk shield for %P gold?", Topic=2 +"sell","sentinel","shield" -> Type=3444, Amount=1, Price=120, "Do you want to sell a sentinel shield for %P gold?", Topic=2 +"sell","salamander","shield" -> Type=3445, Amount=1, Price=280, "Do you want to sell a salamander shield for %P gold?", Topic=2 +"sell","tribal","mask" -> Type=3403, Amount=1, Price=250, "Do you want to sell a tribal mask for %P gold?", Topic=2 +"sell","leopard","armor" -> Type=3404, Amount=1, Price=300, "Do you want to sell a leopard armor for %P gold?", Topic=2 +"sell","horseman","helmet" -> Type=3405, Amount=1, Price=280, "Do you want to sell a horseman helmet for %P gold?", Topic=2 +"sell","feather","headdress" -> Type=3406, Amount=1, Price=850, "Do you want to sell a feather headdress for %P gold?", Topic=2 +"sell","crocodile","boots" -> Type=3556, Amount=1, Price=100, "Do you want to sell crocodile boots for %P gold?", Topic=2 +"sell","bast","skirt" -> Type=3560, Amount=1, Price=750, "Do you want to sell a bast skirt for %P gold?", Topic=2 +"sell","charmer","tiara" -> Type=3407, Amount=1, Price=900, "Do you want to sell a charmer's tiara for %P gold?", Topic=2 +"sell","beholder","helmet" -> Type=3408, Amount=1, Price=2200, "Do you want to sell a beholder helmet for %P gold?", Topic=2 + +"sell","tusk" -> "Sorry, I'm not interested in tusks, but you might want to offer them to Zaidal - as far as I know he uses them for making tables and chairs." +"sell",%1,1<%1,"tusk" -> "Sorry, I'm not interested in tusks, but you might want to offer them to Zaidal - as far as I know he uses them for making tables and chairs." + + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"templar","scytheblade" -> Type=3345, Amount=%1, Price=200*%1, "Do you want to sell %A templar scytheblades for %P gold?", Topic=2 +"sell",%1,1<%1,"ripper","lance" -> Type=3346, Amount=%1, Price=500*%1, "Do you want to sell %A ripper lances for %P gold?", Topic=2 +"sell",%1,1<%1,"hunting","spear" -> Type=3347, Amount=%1, Price=250*%1, "Do you want to sell %A hunting spears for %P gold?", Topic=2 +"sell",%1,1<%1,"banana","staff" -> Type=3348, Amount=%1, Price=1000*%1, "Do you want to sell %A banana staves for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"tusk","shield" -> Type=3443, Amount=%1, Price=850*%1, "Do you want to sell %A tusk shields for %P gold?", Topic=2 +"sell",%1,1<%1,"sentinel","shield" -> Type=3444, Amount=%1, Price=120*%1, "Do you want to sell %A sentinel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"salamander","shield" -> Type=3445, Amount=%1, Price=280*%1, "Do you want to sell %A salamander shields for %P gold?", Topic=2 +"sell",%1,1<%1,"tribal","mask" -> Type=3403, Amount=%1, Price=250*%1, "Do you want to sell %A tribal masks for %P gold?", Topic=2 +"sell",%1,1<%1,"leopard","armor" -> Type=3404, Amount=%1, Price=300*%1, "Do you want to sell %A leopard armors for %P gold?", Topic=2 +"sell",%1,1<%1,"horseman","helmet" -> Type=3405, Amount=%1, Price=280*%1, "Do you want to sell %A horseman helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"feather","headdress" -> Type=3406, Amount=%1, Price=850*%1, "Do you want to sell %A feather headdresses for %P gold?", Topic=2 +"sell",%1,1<%1,"crocodile","boots" -> Type=3556, Amount=%1, Price=100*%1, "Do you want to sell %A pairs of crocodile boots for %P gold?", Topic=2 +"sell",%1,1<%1,"bast","skirt" -> Type=3560, Amount=%1, Price=750*%1, "Do you want to sell %A bast skirts for %P gold?", Topic=2 +"sell",%1,1<%1,"charmer","tiara" -> Type=3407, Amount=%1, Price=900*%1, "Do you want to sell %A charmer's tiaras for %P gold?", Topic=2 +"sell",%1,1<%1,"beholder","helmet" -> Type=3408, Amount=%1, Price=2200*%1, "Do you want to sell %A beholder helmets for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +} diff --git a/data/npc/brewster.npc b/data/npc/brewster.npc new file mode 100644 index 0000000..5b28c32 --- /dev/null +++ b/data/npc/brewster.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brewster.npc: Datenbank für den priester brewster + +Name = "Brewster" +Outfit = (133,57-115-115-95) +Home = [32595,32744,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "G...greetings ." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Uh? Gimme a break. As you can see there's another one first.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye ... and now I'll have a quick drink." + +"bye" -> "Exactly! ", Idle +"farewell" -> * +"job" -> "I am a priest. The worldly representative of the gods so to speak. Not that I would say such a thing of course. This would be vanity after all." +"name" -> "I am ... ah, yes, Brewster. That's me, my name I mean ." +"time" -> "Uhm ... Uh ... No idea, sorry." +"temple" -> "Hehe! Well if you call this hut a temple you are not a devoted churchgoer I guess. But never mind, I won't tell anyone and the gods know it anyway ... if they care." +"king" -> "Ah the king, how lucky he must be - being the ruler of this lovely little piece of dirt here. Hehe." +"venore" -> "Venore, Venore, city of splendour. Hm, the best thing about that city is its brewery." +"thais" -> "Thais!! My beloved hometown! Oh how I miss my good, old Thais." +"carlin" -> "Ha! That's probably even worse than this dump of a jungle here that they call a colony." +"edron" -> "They would never appoint a priest of such a low rank like me to Edron." +"jungle" -> "This jungle must be the way of the gods to give us mortals a taste of hell ." +"gods" -> "Oh come on, just leave me alone. Read a book to find out more." + +"tibia" -> "If Tibia is a fallen god, make your guess what bodypart you are on now. I have my assumptions ... but I won't tell. Hehe." + +"kazordoon" -> "The dwarves I met can't stop to praise the dwarven beer. That wakes the urge in me to ... uhm spread the word of our gods in that city of Kazordoon." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Was never there For all what I have heard it's not that much different from this ugly little settlement." +"elves" -> "After being in that jungle for a while, I can't trust people that love trees anymore." +"elfs" -> * +"darama" -> "The teachings of our temple counts little on this continent. I think it's a sign from the gods to abandon it. But why should anyone listen to poor old Brewster?" +"ankrahmun" -> "Just to think about this cursed town and its inhabitants makes me shiver. I better take a quick drink to forget about it." +"ferumbras" -> " Oh well, he is just that what I'd expect next in all my misery." +"excalibug" -> "Who knows if it is real or just some myth? And who cares at all?" + +"apes" -> "They don't believe me but I have seen them. There are pink apes! They come when I am sleeping and try to steal my beer and wine ." +"lizard" -> "They usually stay away from here so who cares?" +"dworcs" -> "Heard enough of them to dislike them." + +"cough", "syrup" -> "The only person who might have some cough syrup is this druid Ustan. You find him in the tavern. Hmmm the tavern ... " + + +"help",HP<40,! -> "You are hurt my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0,! -> "You are poisoned my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0,! -> "You are burning my child. I will help you.", Burning(0,0), EffectOpp(15) + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + + +"blessing",PvPEnforced,! -> "The vital force of this world is waning. There are no more blessings available on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your vital force is damaged. Each one of the five blessings will reduce this damage." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of Tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just tell me in which of the five blessings you are interested." + +"spiritual", QuestValue(104) > 0 -> "I see you have received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I can sense that the spark of the phoenix has already been given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin have provided you with the embrace of Tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of Tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you have received the blessing of the two suns in the suntower near Ab'Dendriel." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + +"wisdom", QuestValue(101) > 0 -> "I can sense you have already talked to the hermit Eremo on the isle of Cormaya and received this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * +} diff --git a/data/npc/briasol.npc b/data/npc/briasol.npc new file mode 100644 index 0000000..8130091 --- /dev/null +++ b/data/npc/briasol.npc @@ -0,0 +1,105 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# briasol.npc: Datenbank fuer den Juwelier Briasol (Elfenstadt) + +Name = "Briasol" +Outfit = (144,3-86-87-76) +Home = [32635,31667,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "In a few heartbeats I will have time for you %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am a jeweller and exchange money." +"name" -> "I am Briasol Crithanath." +"time" -> "I don't know the time, sorry. I do not care for this concept. Watches are your master, they tell you what to do and when." + +"elves" -> "Our lifespan is longer then that of other races. We should keep that in mind everytime." +"dwarfs" -> "They live that long and make not much out of it." +"humans" -> "I mourn them. As soon as you get to know one he's dead." +"troll" -> "We take care of them, give them shelter, and a reason to live." + +"carlin" -> "Carlin is a quite lovely city, given that its a city of humans." +"thais" -> "Thais has a high demand on the jewelry that I craft." +"venore" -> "The tradesmen of Venore offer high prices for my wares." +"roderick" -> "I have only little dealings with him." +"olrik" -> "I only talk to him when I send a parcel to one of my customers in a far away city. He seems friendly and is a bit eager to please." + +"cenath" -> "They are the ones responsible for most of the magic and the like in this town." +"kuridai" -> "Our caste are workers out of passion." +"deraisim" -> "They hunt for us and patrol the woods." +"abdaisim" -> "I don't know much about them." +"teshial" -> "They are lost in time." +"ferumbras" -> "He will be gone sooner or later." +"crunor" -> "Gods are eternal. They learn so much in their existence." +"excalibug" -> "It's a weapon of times long gone. It's lost for our time." +"news" -> "I know nothing of importance." + +"offer" -> "I can sell gems, pearls, and jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "I have white and black pearls for sale, but you also can sell me some." +"jewel" -> "You can purchase our fine dwarfish wares like wedding rings, golden amulets, and ruby necklaces." +"talon" -> "We don't trade with them." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=5 +"golden","amulet" -> Type=3013, Amount=1, Price=6600,"Do you want to buy a golden amulet for %P gold?", Topic=5 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560,"Do you want to buy a ruby necklace for %P gold?", Topic=5 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=5 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=5 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=5 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=5 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=5 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=5 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=5 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=5 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=5 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1,"Do you want to buy %A ruby necklaces for %P gold?", Topic=5 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=5 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=5 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=5 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=5 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=5 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=5 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=5 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=6 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=6 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=6 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=6 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=6 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=6 +"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 + +Topic=5,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you do not have one." +Topic=6,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=6 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/data/npc/brodrosch.npc b/data/npc/brodrosch.npc new file mode 100644 index 0000000..531c493 --- /dev/null +++ b/data/npc/brodrosch.npc @@ -0,0 +1,66 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# brodrosch.npc: Datenbank für den Kapitän Brodrosch + +Name = "Brodrosch" +Outfit = (66,0-0-0-0) +Home = [32661,31957,15] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N! May Earth protect you, even whilst sailing!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up and wait like the rest, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, yeah, walk, it's cheaper." + +"bye" -> "Earth under your feet ... it's still better than lava.", Idle +"farewell" -> * +"job" -> "Look at my blackened beard? I'm the steamship captain!" +"work" -> * +"name" -> "I am Brodrosch Steamtrousers, son of the machine, of the Molten Rock." +"tibia" -> "Tibia? Just don't ask." +"ship" -> "This is a great ship. Ha! It works without wind but with fire, and it travels not on the ocean but beneath the earth!" +"steamship" -> * +"captain" -> "Of course, I am the captain. But I am also a technomancer." +"technomancer" -> "Being a technomancer is a privilege few dwarfs have. We form earth and fire through powerful technology into tools. Also, we are great inventors." +"inventors" -> "Yes. There could have been thousands of our inventions, if they wouldn't explode all the time..." +"inventions" -> * +"sell" -> "This is not a shop, damn it!" +"buy" -> * +"thais" -> "This is a steamship that travels only subterreneanly. No way to get on that risky ocean. Kazordoon - Cormaya only." +"ab'dendriel" -> * +"carlin" -> * +"venore" -> * +"senja" -> * +"folda" -> * +"vega" -> * +"ice","islands" -> * +"darashia" -> * +"darama" -> * +"kazordoon" -> "Hey, we ARE at Kazordoon! Must be the cavemadness..." +"beer" -> "Sometimes being drunk means seeing two rivers. I survive by steering right between them." +"dwarf" -> "Deep inside, we're all dwarfs." +"gurbasch" -> "Ah, my brother in Cormaya. He can take you back." + +"cormaya" -> Price=160, "So you want to go to Cormaya? %P gold?", Topic=1 +"passage" -> * + +"cormaya",QuestValue(250)>2 -> Price=150, "So you want to go to Cormaya? %P gold?", Topic=1 +"passage",QuestValue(250)>2 -> * + + + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +# für post-quest + +Topic=1,"yes",Premium,QuestValue(227)=4,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3),SetQuestValue(227,5) + +Topic=1,"yes",Premium,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(33309,31989,15), EffectOpp(3) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic=1,"yes" -> "You don't have enough money." +} diff --git a/data/npc/bruno.npc b/data/npc/bruno.npc new file mode 100644 index 0000000..fd95335 --- /dev/null +++ b/data/npc/bruno.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bruno.npc: Der Fischverkäufer Bruno (Fields) + +Name = "Bruno" +Outfit = (128,113-10-95-95) +Home = [32486,31604,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoi, %N. You want to buy some fresh fish?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again!" + +"bye" -> "Good bye and come again!", Idle +"farewell" -> * +"name" -> "My name is Bruno." +"job" -> "My job is to catch fish and to sell them here." +"graubart" -> "I like this old salt. I learned much from him. Whatever. You like some fish? *grin*" +"marlene" -> "Ah yes, my lovely wife. God forgive her, but she can't stop talking. So my work is a great rest for my poor ears. *laughs loudly*" +"aneus" -> "Hmm, I don't know him very well. But he has a very nice story to tell." + +"do","you","sell" -> "Well, I sell freshly caught fish. You like some? Of course, you can buy more than one at once. *grin*" +"offer" -> * + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy a fresh fish for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fresh fishes for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "*grumble* Maybe next time." +} diff --git a/data/npc/budrik.npc b/data/npc/budrik.npc new file mode 100644 index 0000000..36160bf --- /dev/null +++ b/data/npc/budrik.npc @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# budrik.npc: Datenbank für den Minenvorsteher Budrik + +Name = "Budrik" +Outfit = (160,94-76-58-95) +Home = [32524,31906,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, Hiho %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute %N!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"job" -> "I am the foreman of this mine." +"shop" -> * +"name" -> "My name is Budrik Deepdigger, son of Earth, from the Molten Rock." +"time" -> "Precisely %T, young one." +"help" -> "I am a miner, ask someone else." +"dwarfs" -> "We understand the ways of the earth like nobody else does." +"monster" -> "In the deeper mines we discover some nasty beasts now and then." +"dungeon" -> "This is no funhouse. Leave the miners and their drilling-worms alone and get out! We have already enough trouble without you." +"mines" -> * +"trouble" -> "The Horned Fox is leading his bandits in sneak attacks and raids on us." +"horned","fox" -> "A minotaur they threw out at Mintwallin. He must have some kind of hideout nearby." +"hideout" -> "The hideout of the Horned Fox is probably a dangerous if not lethal place for the unexperienced ones." +} diff --git a/data/npc/bunny.npc b/data/npc/bunny.npc new file mode 100644 index 0000000..16fab0b --- /dev/null +++ b/data/npc/bunny.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# bunny.npc: Datenbank für die Generalin Bunny Bonecrusher + +Name = "Bunny Bonecrusher" +Outfit = (139,96-3-79-115) +Home = [32315,31756,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hail","general",! -> "Salutations, commoner %N!" +ADDRESS,"salutations","general",! -> "Salutations, commoner %N!" +ADDRESS,"hello",! -> "Address me properly %N!", Idle +ADDRESS,"hi",! -> "Address me properly %N!", Idle +ADDRESS,"hail",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,"salutations",! -> "Address me with my title, commoner %N!", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE! I am busy!" +BUSY,"hi$",! -> "SILENCE! I am busy!" +BUSY,"hail$",! -> "SILENCE! I am busy!" +BUSY,"salutations$",! -> "SILENCE! I am busy!" +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"farewell" -> * +"news" -> "Our reports are only for internal use." +"report" -> * +"queen" -> "HAIL TO QUEEN ELOISE, OUR NOBLE LEADER!" +"leader" -> "Queen Eloise is a fine leader for our fair town, indeed!" +"job",female -> "I am the general of the queen's army! You really should consider to join, sister." +"job",male -> "I am the general of the queen's army and have not the time to explain this concept to you." +"how","are","you" -> "We are in constant training and in perfect health." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects the defenceless males of our city. Our elite forces are the Green Ferrets." +"guard" -> * +"green","ferrets" -> "Our elite forces are trained by rangers and druids. In the woods they are only second to some elves." +"castle" -> "The castle is not meant for defence but as a residence for the royal family." +"subject" -> "Our citizens have the luck to live under the wise rule of our beloved queen!" +"dogs","of","war" -> "They are a men's club, mainly concerned about bragging and drinking alcohol." +"knights","of","noodles" -> "They are rumoured to be skilled fighters. Then again, in the land of the blind..." +"druid" -> "They are our main magic support and play a major role in our battletactics." +"battletactics" -> "Our tactic is to kiss." +"tactics" -> * +"kiss" -> "K.I.S.S.! Keep It Simple, Stupid! Complicated tactics are to easy to be crushed by a twist of fate." +"bloodblade" -> "Old man. I can't tell what's worse for the shape of Thais' army." +"thais" -> "It's just a rotten hideout for drunks and men too lazy to do some serious work." +"city" -> "Our city blends in with the nature surrounding it. Our druids take care of that." +"bonecrusher" -> "Our family serves in the Carlin army since uncounted generations!" +"sister" -> * +"bambi" -> "She is one of my beloved sisters and serves Carlin as a town guard." +"blossom" -> * +"busty" -> * +"family" -> * +"fenbala" -> "She is one of our Green Ferrets and one of the queen's bodyguards." +"barbara" -> * +"cornelia" -> "Cornelia forges the armor necessary for our troops." +"armor" -> * +"rowenna" -> "Rowenna is responsible for our troops' supply with weapons." +"weapon" -> * +"legola" -> "She is a distant cousin of mine and my sisters." +"ferumbras" -> "Believe it or not. I killed him two times with my own bow, but some unholy forces rise him again and again." +"join" -> "Join what?" +"join","army" -> "Sorry, we dont recruit foreigners. Perhaps you can join by doing a quest for the queen." +"quest",female -> "Sometimes the queen calls for heroines. Keep eyes and ears open!" +"mission",female -> * +"quest",male -> "Yeah. Entrusting a male with an important quest. Get serious!" +"mission",male -> * +"god" -> "I whorship Banor, the first warrior!" +"banor" -> "He is the idol for all fighting women and a reminder of what a man could become, if he could jump over his own shadow!" +"zathroth" -> "Don't mention the dark one in the city of life!" +"monster" -> "We cleared the woods around Carlin from most of them. But lately more and more showed up again." +"excalibug" -> "I am sure only a woman could muster the courage and strength to wield this weapon of myth." +"graveyard" -> "Bah! Just men's tales! Who believes in such bullshit? Perhaps we should put some men there over night and see what happens. Hehehe!" +"cemetary" -> * +"crypt" -> * + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"shit" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(15), EffectMe(5) +Topic=2 -> "You should be careful with your words!" + +-> "Your words don't make any sense to me." +} diff --git a/data/npc/busty.npc b/data/npc/busty.npc new file mode 100644 index 0000000..55e321f --- /dev/null +++ b/data/npc/busty.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# busty.npc: Datenbank für die Wächterin Busty Bonecrusher + +Name = "Busty Bonecrusher" +Outfit = (139,96-19-66-95) +Home = [32294,31791,7] +Radius = 3 + +Behaviour = { +@"guards-carlin.ndb" +} diff --git a/data/npc/captain1.npc b/data/npc/captain1.npc new file mode 100644 index 0000000..3d45d56 --- /dev/null +++ b/data/npc/captain1.npc @@ -0,0 +1,81 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain1.npc: Kapitän Blaubaer in Thais + +Name = "Captain Bluebear" +Outfit = (129,19-69-107-50) +Home = [32310,32210,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Bluebear from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Carlin, Ab'Dendriel, Venore, Port Hope or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghostship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> "This is Thais. Where do you want to go?" +"carlin" -> Price=110, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=130, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron" -> Price=160, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=170, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope" -> Price=160, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + + +"carlin",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + + +Topic>0,Topic<8,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=3,"yes",PZBlock,! -> * +#Topic=4,"yes",PZBlock,! -> * +#Topic=5,"yes",PZBlock,! -> * + +# für postquest +Topic=2,"yes",Premium, QuestValue(227)=1,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11),SetQuestValue(227,2) + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/data/npc/captain2.npc b/data/npc/captain2.npc new file mode 100644 index 0000000..d65f59b --- /dev/null +++ b/data/npc/captain2.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain2.npc: Kapitän Greyhound in Carlin + +Name = "Captain Greyhound" +Outfit = (129,96-113-95-115) +Home = [32388,31822,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Greyhound from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Ab'Dendriel, Venore or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=110, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> "This is Carlin. Where do you want to go?" +"ab'dendriel" -> Price=80, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron" -> Price=110, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=130, "Do you seek a passage to Venore for %P gold?", Topic=5 + +"thais",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Thais for %P gold?", Topic=1 +"ab'dendriel",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"edron",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Venore for %P gold?", Topic=5 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=3,"yes",PZBlock,! -> * +Topic=4,"yes",PZBlock,! -> * +Topic=5,"yes",PZBlock,! -> * + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/data/npc/captain3.npc b/data/npc/captain3.npc new file mode 100644 index 0000000..44f68d6 --- /dev/null +++ b/data/npc/captain3.npc @@ -0,0 +1,73 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain3.npc: Kapitän Seagull in Ab'Dendriel + +Name = "Captain Seagull" +Outfit = (129,60-113-95-115) +Home = [32735,31668,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Seagull from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Venore or Edron?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"ankrahmun" -> "I'm sorry, but we don't serve this route." +"tiquanda" -> * +"port","hope" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=130, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=80, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> "This is Ab'Dendriel. Where do you want to go?" +"edron" -> Price=70, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=90, "Do you seek a passage to Venore for %P gold?", Topic=5 + +"thais",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=60, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Venore for %P gold?", Topic=5 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * + +Topic=4,"yes",PZBlock,! -> * +Topic=5,"yes",PZBlock,! -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/data/npc/captain4.npc b/data/npc/captain4.npc new file mode 100644 index 0000000..f64cfb7 --- /dev/null +++ b/data/npc/captain4.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain4.npc: Kapitän Seahorse in Edron + +Name = "Captain Seahorse" +Outfit = (129,19-113-95-115) +Home = [33176,31764,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Seahorse from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Ab'Dendriel, Venore, Port Hope, Ankrahmun or the isle Cormaya?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"darashia" -> "I'm not sailing there. This route is afflicted by a ghost ship! However I've heard that Captain Fearless from Venore sails there." +"darama" -> * +"ghost" -> "Many people who sailed to Darashia never returned because they were attacked by a ghostship! I'll never sail there!" + +"thais" -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=110, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=70, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"cormaya" -> Price=20, "Do you seek a passage to Cormaya for %P gold?", Topic=4 +"edron" -> "This is Edron. Where do you want to go?" +"venore" -> Price=40, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun" -> Price=160, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope" -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + +"thais",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=60, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"cormaya",QuestValue(250)>2 -> Price=10, "Do you seek a passage to Cormaya for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=30, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 +"port","hope",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Port Hope for %P gold?", Topic=7 + + +Topic>0,Topic<8,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=2,"yes",PZBlock,! -> * +#Topic=3,"yes",PZBlock,! -> * +#Topic=4,"yes",PZBlock,! -> * +#Topic=5,"yes",PZBlock,! -> * +#Topic=6,"yes",PZBlock,! -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) + +# für post-quest +Topic=5,"yes",Premium,QuestValue(227)=3,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11),SetQuestValue(227,4) + +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/data/npc/captain5.npc b/data/npc/captain5.npc new file mode 100644 index 0000000..2b44363 --- /dev/null +++ b/data/npc/captain5.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# captain5.npc: Kapitän Fearless in Venore + +Name = "Captain Fearless" +Outfit = (129,19-113-95-115) +Home = [32955,32022,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "My name is Captain Fearless from the Royal Tibia Line." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "The Royal Tibia Line connects all seaside towns of Tibia." +"line" -> * +"company" -> * +"route" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Carlin, Ab'Dendriel, Port Hope, Edron, Darashia or Ankrahmun?" +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"ice" -> "I'm sorry, but we don't serve the routes to the Ice Islands." +"senja" -> * +"folda" -> * +"vega" -> * +"ghost" -> "There's a legend of a ghostship cruising between Venore and Darashia. Many captains are afraid to sail this route. Hah, but not me!" + +"thais" -> Price=170, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin" -> Price=130, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel" -> Price=90, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"venore" -> "This is Venore. Where do you want to go?" +"darashia" -> Price=60, "Do you seek a passage to Darashia for %P gold?", Topic=5 +"edron" -> Price=40, "Do you seek a passage to Edron for %P gold?", Topic=4 +"ankrahmun" -> Price=150, "Do you seek a passage to Ankrahmun for %P gold?", Topic=7 +"port","hope" -> Price=160, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + + +"thais",QuestValue(250)>2 -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"carlin",QuestValue(250)>2 -> Price=120, "Do you seek a passage to Carlin for %P gold?", Topic=2 +"ab'dendriel",QuestValue(250)>2 -> Price=80, "Do you seek a passage to Ab'Dendriel for %P gold?", Topic=3 +"darashia",QuestValue(250)>2 -> Price=50, "Do you seek a passage to Darashia for %P gold?", Topic=5 +"edron",QuestValue(250)>2 -> Price=30, "Do you seek a passage to Edron for %P gold?", Topic=4 +"ankrahmun",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Ankrahmun for %P gold?", Topic=7 +"port","hope",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + + +Topic>0,Topic<9,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=2,"yes",PZBlock,! -> * +#Topic=3,"yes",PZBlock,! -> * +#Topic=4,"yes",PZBlock,! -> * +#Topic=5,"yes",PZBlock,! -> * +#Topic=7,"yes",PZBlock,! -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32387,31821,6), EffectOpp(11) +Topic=3,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32733,31668,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) + +Topic=5,"yes",Premium,CountMoney>=Price -> "I warn you! This route is haunted by a ghostship. Do you really want to go there?", Topic=6 +Topic=6,"yes",Premium,CountMoney>=Price,Random(1,10)=1 -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33330,32172,5), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) + +Topic=7,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} diff --git a/data/npc/captain6.npc b/data/npc/captain6.npc new file mode 100644 index 0000000..fb6ede0 --- /dev/null +++ b/data/npc/captain6.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# petros.npc: Fährmann Petros bei Darashia + +Name = "Petros" +Outfit = (128,79-10-127-127) +Home = [33289,32481,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. I can take you to Venore, Port Hope or Ankrahmun if you like." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye!" + +"bye" -> "Good bye!", Idle +"farewell" -> * +"name" -> "My name is Petros." +"job" -> "I take along people to Venore, Port Hope and Ankrahmun." +"ghost" -> "Oh, I don't believe in ghosts." + +"ship" -> "My boat is ready to bring you to Venore, Port Hope or Ankrahmun." +"boat" -> * +"passage" -> * +"venore" -> Price=60, "Do you want to get to Venore for %P gold?", Topic=1 +"ankrahmun" -> Price=100, "Do you want to get to Ankrahmun for %P gold?", Topic=2 +"port","hope" -> Price=180, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + + +"venore",QuestValue(250)>2 -> Price=50, "Do you want to get to Venore for %P gold?", Topic=1 +"ankrahmun",QuestValue(250)>2 -> Price=90, "Do you want to get to Ankrahmun for %P gold?", Topic=2 +"port","hope",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + +Topic>0,Topic<9,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=2,"yes",PZBlock,! -> * + +Topic=1,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=2,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "Maybe later." +} diff --git a/data/npc/captain7.npc b/data/npc/captain7.npc new file mode 100644 index 0000000..e9d82c9 --- /dev/null +++ b/data/npc/captain7.npc @@ -0,0 +1,69 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sinbeard.npc: Kapitän Sinbeard in Ankrahmun + +Name = "Captain Sinbeard" +Outfit = (134,95-10-56-77) +Home = [33094,32884,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome on board, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome on board, Madam %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N. You're next in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Recommend us, if you were satisfied with our service." + +"bye" -> "Good bye. Recommend us, if you were satisfied with our service.", Idle +"farewell" -> * +"name" -> "I am known all over the world as Captain Sinbeard." +"job" -> "I am the captain of this sailing-ship." +"captain" -> * +"ship" -> "My ship is the fastest in the whole world." +"line" -> * +"company" -> * +"tibia" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Darashia, Venore, Port Hope or Edron?" +"route" -> * +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * +"thais" -> "I'm sorry but my ship does not currently service that port." +"carlin" -> * +"ab'dendriel" -> * + +"darashia" -> Price=100, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron" -> Price=160, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=150, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope" -> Price=80, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + + +"darashia",QuestValue(250)>2 -> Price=90, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Venore for %P gold?", Topic=5 +"port","hope",QuestValue(250)>2 -> Price=70, "Do you seek a passage to Port Hope for %P gold?", Topic=8 + +Topic>0,Topic<9,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=4,"yes",PZBlock,! -> * +#Topic=5,"yes",PZBlock,! -> * + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=8,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32530,32784,6), EffectOpp(11) + +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." +} + diff --git a/data/npc/carina.npc b/data/npc/carina.npc new file mode 100644 index 0000000..7c928bd --- /dev/null +++ b/data/npc/carina.npc @@ -0,0 +1,40 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# carina.npc: Datenbank für die Juwelierin Carina + +Name = "Carina" +Outfit = (138,97-70-94-76) +Home = [33015,32048,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N. I am looking forward to trade with you." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please, %N, give me another minute with our other customer first.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell some of the most beautiful jewels of the lands." +"name" -> "I am Carina Carlson." +"time" -> "It's %T." +"offer" -> "I am selling jewels, just have a look." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"jewel" -> "We offer wedding rings, golden amulets, and ruby necklaces." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, make sure to come back, as soon as you have enough money." +Topic=1 -> "Perhaps next time." +} diff --git a/data/npc/charles.npc b/data/npc/charles.npc new file mode 100644 index 0000000..a170e9d --- /dev/null +++ b/data/npc/charles.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# charles.npc: Datenbank für den Kapitän Charles + +Name = "Charles" +Outfit = (134,57-29-95-98) +Home = [32529,32785,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahoi." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Just wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am the captain of the Poodle, the proudest ship on all oceans." +"name" -> "It's Charles." +"time" -> "It is precisely %T." +"king" -> "His majesty himself was present at the day the Poodle was launched." + +"jungle" -> "It's a fascinating forest, full of exotic life. If it weren't for my duties, I would spend some time just exploring this jungle." + +"tibia" -> "We live in a fascinating world with even more fascinating oceans. And all its major harbours are known to me." +"major","harbour" -> "Well the harbours of thais, venore, carlin, edron, darashia and ankrahmun. Do you have any questions about one of those harbours?", Topic=20 +Topic=20,"venore" -> "The Venorans build fine ships. Enough said about them." +Topic=20,"thais" -> "Thais is the proud capital of the largest kingdom in the known world." +Topic=20,"carlin" -> "Rebellious women might be amusing for a while, but it is time for them to stop this nonsense and return to the kingdom." +Topic=20,"edron" -> "The coastline of Edron is treacherous and it takes some skills to sail a ship safely into the harbour." +Topic=20,"darashia" -> "An unremarkable little town with a small harbour and quiet people." +Topic=20,"ankrahmun" -> "The city is surely worth a look although its inhabitants are somewhat strange and their customs oddish." + +"kazordoon" -> "An inland town of dwarves, somewhere in the middle of nowhere." +"dwarves" -> "It's fun to see a seasoned dwarven fighter turnining into a shivering green something as soon as we get a mild breeze on sea." +"dwarfs" -> * +"ab'dendriel" -> "My visits there were interesting and I learnt a lot about the elves and their city. I can only recommend a visit there and if it is only to admire the amazing architectural style in which the city was built." +"elves" -> "Elves are very special creatures. They keep in touch with nature almost like druids. Although I don't really understand their way of life, I think we could learn one or two things of them." +"elfs" -> * +"darama" -> "I sailed around the whole continent once and I have seen many of its wonders. For sure there are more waiting to be discovered." + +"ferumbras" -> "He is that for the land what giant sea serpents are for the sea." +"excalibug" -> "You better ask some knight about it." +"apes" -> "I would love to catch a living exemplar and bring it to Thais so the king could see it." +"lizard" -> "They have a small settlement in the southeast of the jungle next to the coast. It looks somewhat primitive but there is evidence it was erected only recently." +"dworcs" -> "They attacked us when we set our feet on the south shore of the continent. They are poison using savages, nothing more." + +"thais" -> Price=160, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia" -> Price=180, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron" -> Price=150, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore" -> Price=160, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun" -> Price=110, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 + +"thais",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Thais for %P gold?", Topic=1 +"darashia",QuestValue(250)>2 -> Price=170, "Do you seek a passage to Darashia for %P gold?", Topic=2 +"edron",QuestValue(250)>2 -> Price=140, "Do you seek a passage to Edron for %P gold?", Topic=4 +"venore",QuestValue(250)>2 -> Price=150, "Do you seek a passage to Venore for %P gold?", Topic=5 +"ankrahmun",QuestValue(250)>2 -> Price=100, "Do you seek a passage to Ankrahmun for %P gold?", Topic=6 + +Topic>0,Topic<8,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=4,"yes",PZBlock,! -> * +#Topic=5,"yes",PZBlock,! -> * +#Topic=6,"yes",PZBlock,! -> * +#Topic=1,"yes",PZBlock,! -> * + + +Topic=1,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32312,32211,6), EffectOpp(11) +Topic=2,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33290,32481,6), EffectOpp(11) +Topic=4,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=5,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(32954,32023,6), EffectOpp(11) +Topic=6,"yes",Premium,CountMoney>=Price -> "Set the sails!", DeleteMoney, Idle, EffectOpp(11), Teleport(33091,32883,6), EffectOpp(11) +Topic>0,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel on board of our ships." +Topic>0,"yes" -> "You don't have enough money." +Topic>0 -> "We would like to serve you some time." + +"passenger" -> "We would like to welcome you on board." +"trip" -> "Where do you want to go? To Thais, Darashia, Venore, Ankrahmun or Edron?" +"route" -> * +"passage" -> * +"town" -> * +"destination" -> * +"sail" -> * +"go" -> * + +} diff --git a/data/npc/chatterbone.npc b/data/npc/chatterbone.npc new file mode 100644 index 0000000..3222961 --- /dev/null +++ b/data/npc/chatterbone.npc @@ -0,0 +1,128 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chatterbone.npc: Datenbank für den Magiehändler Chatterbone + +Name = "Chatterbone" +Outfit = (18,0-0-0-0) +Home = [32981,32080,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "... Greeeeeetiiiingssss" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "... Wait... %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "... Good... Bye" + +"bye" -> "... Good... Bye", Idle +"farewell" -> * +"job" -> "... Selling Spells" +"name" -> "... Chatterbone" +"time" -> "... Time?... Not important... anymore." +"king" -> "..." +"tibianus" -> * +"vladruc" -> "... Maaaaassssterrrrr" +"urghain" -> * +"ferumbras" -> "... un...important" +"market" -> "... You buy?" +"excalibug" -> "... we hid it... so long ago... so long..." +"news" -> "... they build a new city... Carlin shall be its name..." +"flaming","pit" -> "... we conquered them... held them so long... long ago..." +"pits","inferno" -> * +"nightmare","pit" -> * + +"sorcerer" -> "... You... buy spells?" +"power" -> * +"druid" -> "... Ask Smiley..." +"spellbook" -> "... You buy book... store spells... other counter..." +"rune" -> "... Runes... mighty stones... other counter..." +Sorcerer,"spell" -> "... Spells... rune spells... instant spells... what you want? ... Or for which level?", Topic=2 +"spell" -> "... Only sorcerers..." + +Topic=2,"rune","spell" -> "... Attack rune spells ... support rune spells ... Which...?" +Topic=2,"instant","spell" -> "... Attack spells ... healing spells ... supply spells ... support spells ... summon spells. Which...?" +Topic=2,"level" -> "Which level...?", Topic=2 +Topic=2,"bye" -> "... Good... Bye", Idle + +sorcerer,"wand",QuestValue(333)<1 -> "Oooh... present from meee... take it... goooood start for youuuung sorcerers...",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +Sorcerer,"level" -> "Which level...?", Topic=2 +Sorcerer,"rune","spell" -> "... Attack rune spells ... support rune spells ... Which...?" +Sorcerer,"instant","spell" -> "... Attack spells ... healing spells ... supply spells ... support spells ... summon spells. Which...?" + +Sorcerer,"attack","rune","spell" -> "... Missile rune spells ... explosive rune spells ... field rune spells ... wall rune spells ... bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category ... 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category ... 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category ... 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category ... 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category ... 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category ... 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category ... 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category ... 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category ... 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category ... 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "... You want 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "... You want 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "... You want 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "... You want 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "... You want 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "... You want 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "... You want 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "... You want 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "... You want 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "... You want 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "... You want 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "... You want 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "... You want 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "... You want 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "... You want 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "... You want 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "... You want 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "... You want 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "... You want 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "... You want 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "... You want 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "... You want 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "... You want 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "... You want 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "... You want 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "... You want 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "... You want 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "... You want 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "... You want 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "... For level 8 ... 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "... For level 9 ... 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "... For level 10 ... 'Antidote'.", Topic=2 +Topic=2,"11$" -> "... For level 11 ... 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "... For level 13 ... 'Great Light'.", Topic=2 +Topic=2,"14$" -> "... For level 14 ... 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "... For level 15 ... 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "... For level 17 ... 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "... For level 18 ... 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "... For level 20 ... 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "... For level 23 ... 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "... For level 25 ... 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "... For level 27 ... 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "... For level 29 ... 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "... For level 31 ... 'Explosion'.", Topic=2 +Topic=2,"33$" -> "... For level 33 ... 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "... For level 35 ... 'Invisible'.", Topic=2 +Topic=2,"38$" -> "... For level 38 ... 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "... For level 41 ... 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "... For level 45 ... 'Sudden Death'.", Topic=2 + +Topic=2 -> "... No spells for this level ... but for many ... from 8 to 45.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "... You already know..." +Topic=3,"yes",Level Amount=SpellLevel(String), "... not level %A..." +Topic=3,"yes",CountMoney "... More money." +Topic=3,"yes" -> "... Here...", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "... Then not." +} diff --git a/data/npc/chemar.npc b/data/npc/chemar.npc new file mode 100644 index 0000000..20e4005 --- /dev/null +++ b/data/npc/chemar.npc @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chemar.npc: Datenbank für den Teppichpiloten Chemar in Darashia + +Name = "Chemar" +Outfit = (130,95-3-14-76) +Home = [33270,32439,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, the wind brings in another visitor. Feel welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N! Be calm as the eye of the storm, and your patience will be rewarded.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings!" + +"bye" -> "Daraman's blessings!", Idle +"name" -> "My name is Chemar Ibn Kalith." +"job" -> "I am a licensed carpetpilot and responsible for the Darashian airmail. I can bring you to the Femor Hills, Edron, or you can buy letters and parcels." +"time" -> "It's %T, precisely." +"caliph" -> "The caliph depends heavily on his carpetfleet for commerce and for war alike." +"kazzan" -> * +"daraman" -> "The prophet of our people; praised be his name." +"ferumbras" -> "This scourge of the west may have connections to the evil soils in Drefia." +"drefia" -> "In the west a big city existed. Its people were corrupted and drew the wrath of the djinn upon them and Drefia was destroyed." +"excalibug" -> "I have been almost everywhere in the world and think it's only a myth." +"thais" -> "I think it's a rolemodel for what befalls people if they forget the teachings of Daraman." +"carlin" -> "That city is getting noisier and more crowded each month." +"news" -> "Our carpetpilots bring in too many news to recall them all." +"rumour" -> * +"rumor" -> * +"flying","carpet" -> "Do you want to buy a flying carpet for 5000 platinum coins?", Price=500000, Topic=7 +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 + +"passage" -> "I can fly you to Femor Hills or Edron if you like. Where do you want to go?" +"fly" -> * +"go" -> * +"transport" -> * +"ride" -> * +"trip" -> * +"tibia" -> * + +"femur" -> "Are you sure that you are not talking about the FEMOR Hills?" +"hill" -> Price=60, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=4 +"femor" -> * +"edron" -> Price=40, "Do you want to get a ride to Edron for %P gold?", Topic=5 + +"hill",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=4 +"femor",QuestValue(250)>2 -> * +"edron",QuestValue(250)>2 -> Price=30, "Do you want to get a ride to Edron for %P gold?", Topic=5 + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Darashian Airmail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." + + +Topic=4,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=5,"yes",PZBlock,! -> * + +Topic=4,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +Topic=4,"yes" -> "You don't have enough money." +Topic=4 -> "You shouldn't miss the experience." + +Topic=5,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +Topic=5,"yes" -> "You don't have enough money." +Topic=5 -> "You shouldn't miss the experience." + +Topic=7,"yes",CountMoney>=Price -> "Oh, I am sorry, but you have no pilot licence." +Topic=7,"yes" -> "You don't own enough worldly wealth to afford this item." +Topic=7 -> "Maybe another day then, my friend." +} diff --git a/data/npc/chephan.npc b/data/npc/chephan.npc new file mode 100644 index 0000000..9892038 --- /dev/null +++ b/data/npc/chephan.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chephan.npc: Datenbank für den Küchenbedarfshändler Chephan + +Name = "Chephan" +Outfit = (128,2-26-115-76) +Home = [32890,32077,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, looking for some cooking gear today, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute, %N. I am talking already, but will be avaliable for you very soon.", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "So long, %N." + +"bye" -> "So long, %N.", Idle +"name" -> "I am Chephan, at your service." +"job" -> "I sell all the cooking gear you can dream of." +"warehouse" -> "Here you can by so many things you will need one day or another. Just have a look." +"time" -> "Watches are sold in the south east part of this warehouse." +"king" -> "Even a king needs a fork now and then. To scratch his back or to poke servants for example." +"tibianus" -> * +"army" -> "They brought most of their cooking gear from thais." +"ferumbras" -> "See this fork? Now imagine what a hero like you could do to an evil sorcerer with that fork! Care to buy one?" +"excalibug" -> "Just an oversized kitchenknife. Better buy the real thing." +"thais" -> "Thaian cooking gear is of inferior quality. Make sure to upgrade yours here as soon as you can." +"tibia" -> "The world is flat as this plate. You should buy one as a symbol for Tibia." +"carlin" -> "So many women and so little intrest in cooking, horrible." +"news" -> "My recipies are family secrets, sorry." +"tax" -> "Those taxes are killing me. And they are getting worse each year!" +"privilege" -> "I don't feel that privileged. In fact our beloved city is bleeding for the profit of Thais." +"gambling" -> "Thanks to that taxes I have not enough spare money to gamble much." + +"offer" -> "That would be: Buckets, bottles, mugs, cups, jugs, plates, baking trays, pots, pans, forks, spoons, knifes, wooden spoons, cleavers, spatulas, and rolling pins." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"baking","tray" -> Type=3464, Amount=1, Price=20, "Do you want to buy a baking tray for %P gold?", Topic=1 +"pot" -> Type=3465, Amount=1, Price=30, "Do you want to buy a pot for %P gold?", Topic=1 +"pan" -> Type=3466, Amount=1, Price=20, "Do you want to buy a pan for %P gold?", Topic=1 +"fork" -> Type=3467, Amount=1, Price=10, "Do you want to buy a fork for %P gold?", Topic=1 +"spoon" -> Type=3468, Amount=1, Price=10, "Do you want to buy a spoon for %P gold?", Topic=1 +"knife" -> Type=3469, Amount=1, Price=10, "Do you want to buy a knife for %P gold?", Topic=1 +"wooden","spoon" -> Type=3470, Amount=1, Price=5, "Do you want to buy a wooden spoon for %P gold?", Topic=1 +"cleaver" -> Type=3471, Amount=1, Price=15, "Do you want to buy a cleaver for %P gold?", Topic=1 +"spatula" -> Type=3472, Amount=1, Price=12, "Do you want to buy an oven spatula for %P gold?", Topic=1 +"rolling","pin" -> Type=3473, Amount=1, Price=12, "Do you want to buy a rolling pin for %P gold?", Topic=1 + +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"baking","tray" -> Type=3464, Amount=%1, Price=20*%1, "Do you want to buy %A baking trays for %P gold?", Topic=1 +%1,1<%1,"pot" -> Type=3465, Amount=%1, Price=30*%1, "Do you want to buy %A pots for %P gold?", Topic=1 +%1,1<%1,"pan" -> Type=3466, Amount=%1, Price=20*%1, "Do you want to buy %A pans for %P gold?", Topic=1 +%1,1<%1,"fork" -> Type=3467, Amount=%1, Price=10*%1, "Do you want to buy %A forks for %P gold?", Topic=1 +%1,1<%1,"spoon" -> Type=3468, Amount=%1, Price=10*%1, "Do you want to buy %A spoons for %P gold?", Topic=1 +%1,1<%1,"knife" -> Type=3469, Amount=%1, Price=10*%1, "Do you want to buy %A knives for %P gold?", Topic=1 +%1,1<%1,"wooden","spoon" -> Type=3470, Amount=%1, Price=5*%1, "Do you want to buy %A wooden spoons for %P gold?", Topic=1 +%1,1<%1,"cleaver" -> Type=3471, Amount=%1, Price=15*%1, "Do you want to buy %A cleavers for %P gold?", Topic=1 +%1,1<%1,"spatula" -> Type=3472, Amount=%1, Price=12*%1, "Do you want to buy %A oven spatulas for %P gold?", Topic=1 +%1,1<%1,"rolling","pin" -> Type=3473, Amount=%1, Price=12*%1, "Do you want to buy %A rolling pins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Please come back with more money." +Topic=1 -> "I hope next time." +} diff --git a/data/npc/chester.npc b/data/npc/chester.npc new file mode 100644 index 0000000..dc3d4b4 --- /dev/null +++ b/data/npc/chester.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chester.npc: Datenbank für Chester, den Chef des TBI + +Name = "Chester Kahs" +Outfit = (131,10-28-47-95) +Home = [32348,32184,6] +Radius =2 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations, stranger." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am very busy %N." +BUSY,"hi$",! -> "Sorry, I am very busy %N." +BUSY,! -> NOP +VANISH,! -> "Take care out there!" + +"bye" -> "Take care out there!", Idle +"farewell" -> * +"news" -> "Sorry, almost news that are a little interesting are confidential." +"how","are","you"-> "I am troubled by all the mysteries out there." +"sell" -> "I am no tradesman, sorry." +"king" -> "King Tibianus III is our leader and my direct superior." +"superior" -> "I report directly to the king himself." +"report" -> "My reports are confidential and for the ears and eyes of the king only." +"job" -> "I am the head of the TBI." +"tbi$" -> "The Tibian Bureau of Investigation, the secret service of His Royal Highness." +"investigation" -> "We collect information about people and incidents." +"bureau" -> * +"people" -> "We know much about the citizens and some other people." +"citizen" -> "I only can give you some official information about our citizens. About whom do you wish to talk?" +"incident" -> "There are things that must be kept secret." +"secret" -> "Certain information is not for the eyes and ears of everyone. Please understand that." +"army" -> "Our army might be infested with spies already." +"spies" -> "Polymorphed Minotaurs, shapechanging demons, possessed innocents ... who can tell for sure." +"guard" -> "I think we can't trust the guards anymore." +"trust" -> "Too many possibilities to become a servant of darkness to trust ANYONE!" +"castle" -> "The castle isn't safe! I warned them of the entrance to the dungeons, but no one is litstening. How many people have to die before they do something about that?" +"dogs","of","war"-> "Even they can't stop a handful of demons." +"red","guard" -> "They are at my command now and then ... but it's a mistake to rely on anyone except yourself." +"secret","police"-> "Are you joking? What's secret in Tibia at all?" +"silver","guard" -> "The king's best. But is the best good enough to fight what stalks the nights?" +"city" -> "The city is open to almost everyone. That literally opens doors for all kinds of criminals and fiends." +"criminal" -> "There are so many murderers and thiefs out there that I wonder if there is some greater force of evil subtly encouraging that." +"fiend" -> "Not everything that walks our streets is human ... or even living." +"stutch" -> "He is one of the few people I can trust." +"harsky" -> * +"bozo" -> "He isn't the fool he pretends to be. So to what is he up to?" +"sam" -> "I say it was a mistake to rely on a single person for such vital services but having those venoreans here is even worse." +"gorn" -> "A man too concerned about profit to be trustworthy. This kind of man sells his soul to the highest bidder. It's just a question if he has done it already or will do it soon." +"frodo" -> "Have you noticed how easy it would be to poison his supplies and kill a great deal of people with ease?" +"benjamin" -> "Something happened to him that snapped his mind. Can we be sure what more might have happened to him unnoticed?" +"lugri" -> "At least you KNOW that you have to expect only evilness from this guy and that's the best one can say about him." +"gods" -> "We are just the pawns of the gods. The best we can expect is that our play amuses them enough to keep their interest in us so we might live a day or two longer." +"lynda" -> "She puts her trust in the help of beings she can't comprehend. Think by yourself if that's clever." +"quentin" -> "A peaceful man. But in our days peace is just an illusion. We are surrounded by enemies and dangers." +"enemy" -> "The people of the northern city, the minotaurs, the followers of Zathroth, the demons, and countless others!" +"enemies" -> * +"danger" -> "Danger is common like day and night for a Tibian, who keeps his eyes open." +"dungeon" -> "Monsters lurk in each corner of the dungeons, which spread beneath us, breeding in the shadows and plotting to destroy us all." +"ferumbras" -> "Some say he's the avatar of Zathroth himself, but perhaps the truth about him is even darker then the worst rumours can imagine." +"demon" -> "They say there are just two of them in the underground ruins! These damned fools! There are dozens of them! And the two they already saw are only some of the weakest of demonkind!" +"underground","ruin"-> "We have no clue what happened to the civilization that once dwelled underground, but their complete extinction should be a warning for us!" +"mcronald" -> "Have you ever wondered what these caves under their farm are good for? And have you noticed how many adventurers go down there and never return? Well, think about it!" +"sorcerer" -> "I don't know where they got their secret spells in the first place, nor did most of them know ... If I were a sorcerer that would be a fact to give me nightmares." +"knight" -> "It's too easy to become a knight. They take almost everyone. And if you look in the streets you can see what happens if you give training and a flashy title to almost everyone." +"paladin" -> "They should be noble warriors, but does it take bravery to shoot someone from a certain distance? The former paladins were virtuous heroes, the ones you meet today are just simple treasure hunters." +"druid" -> "It is said that druids are preservers of life and good aligned, but let me ask you if it's so 'good' to sell runes to the highest bidder, no matter who that might be? I think you get the point!" +"truth" -> "The dungeons are full of hideous monsters, unnamed terrors, unsolved riddles ... and maybe some answers. Believe me! The truth is down there ... somewhere!" +"ruthless","seven"-> "We know little about them. But even that gives me nightmares! But it's your lucky day, since this information is confidential, and so it can't bother you." +"aruda" -> "This woman is a clever thief, so watch out when you are talking to her." +"partos" -> "This criminal was wanted for many crimes. At last he got caught and put to jail." +"excalibug" -> "We are surrounded by myths, living and dead. How can someone doubt that there IS something like Excalibug somewhere?" +"necromant","nectar" -> "Followers of evil are investigating about that, though I guess even they don't know what it's good for. Perhaps just a myth of evil." + +"rebellion" -> "I have far too few information about the rebellion, but we suspect the followers of Zathroth behind it." +"berfasmur","is","ferumbras" -> "Yes, thats what I figured out, too. Just one of his disguises." +"berfasmur" -> "Strange name, isn't it? Play around with the letters and you are in for a surprise." +"gamel","rebel" -> "Are you saying that Gamel is a member of the rebellion?", Topic=1 + +Topic=1,"no" -> "Then don't bother me with that. I am a busy man." +Topic=1,"yes" -> "Do you know what his plans are about?", Topic=2 +Topic=2,"magic","crystal","lugri","deathcurse" -> Type=3061, Amount=1, "That is terrible! Will you give me the crystal?", Topic=3 +Topic=2 -> "Tell me precisely what he asked you to do! What, to whom, and what for! It's important!", Topic=2 +Topic=3,"no" -> "Traitor!", Burning(25,25), EffectOpp(6), EffectMe(14), Delete(Type), Idle +Topic=3,"yes",Count(Type)>=Amount -> "Thank you! Take this ring. If you ever need a healing, come, bring the scroll, and ask me to 'heal'.", Delete(Type), Type=3052, Amount=1, Create(Type) +Topic=3,"yes",Count(Type) "Sorry, you have none." +"heal" -> Type=3052, Amount=1, "Do you need the healing now?", Topic=4 +Topic=4,"no" -> "As you whish." +Topic=4,"yes",Count(Type)>=Amount -> "So be healed!", Delete(Type), HP=1000, EffectOpp(13) +Topic=4,"yes",Count(Type) "Sorry, you are not worthy!" +} diff --git a/data/npc/christoph.npc b/data/npc/christoph.npc new file mode 100644 index 0000000..82e11be --- /dev/null +++ b/data/npc/christoph.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# christoph.npc: Datenbank für eine Stadtwache in Venore + +Name = "Christoph" +Outfit = (131,113-113-113-115) +Home = [32885,32031,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/chrystal.npc b/data/npc/chrystal.npc new file mode 100644 index 0000000..5326cf0 --- /dev/null +++ b/data/npc/chrystal.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# chyrstal.npc: Datenbank für die Postfrau Chrystal + +Name = "Chrystal" +Outfit = (136,116-79-117-76) +Home = [33174,31811,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "At your service %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please be patient %N. I'll be with you in moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Who is next?" + +"bye" -> "Who is next?", Idle +"name" -> "My name is Chrystal." +"job" -> "I am responsible for this post office." + +"kevin" -> "Mr. Postner is the leader of our guild and the most prominent postofficer in the whole land." +"postner" -> * +"postmasters","guild" -> "Yes, our guild is the lifeblood of the tibia cominity so to say." +"join" -> "You can apply to join only at our headquarter." +"headquarter" -> "You can find it on the road from Thais to Kazordoon." + + +"measurements",QuestValue(234)>0,QuestValue(241)<1 -> "If its necessary ... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(241,1) + +"time" -> "It is %T right now." +"king" -> "Hail to the king!" +"tibianus" -> * +"army" -> "The army ensures the safety of the traderoutes and of our mail system." +"ferumbras" -> "I bet he never gets any letters." +"excalibug" -> "Better ask knights about that." +"tibia" -> "Our post system spans the entire known world." +"thais" -> "We deliver letters and parcels even there." +"carlin" -> * +"kazordoon" -> * +"ab'dendriel" -> * +"edron" -> "Our post system even delivers letters and parcels to and from this isle." +"news" -> "Sorry, that's postal secret." +"rumors" -> * + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(110,8) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(110,6), Create(110,10) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/cipfried.npc b/data/npc/cipfried.npc new file mode 100644 index 0000000..64e9479 --- /dev/null +++ b/data/npc/cipfried.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cipfried.npc: Datenbank fuer den Moench Cipfried + +Name = "Cipfried" +Outfit = (57,0-0-0-0) +Home = [32097,32217,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, %N! Feel free to ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N. I already talk to someone!", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad, %N. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Well, bye then." + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "I am just a humble monk. Ask me if you need help or healing." +"name" -> "My name is Cipfried." +"monk" -> "I sacrifice my life to serve the good gods of Tibia." +"tibia" -> "That's where we are. The world of Tibia." +"rookgaard" -> "The gods have chosen this isle as the point of arrival for the newborn souls." +"god" -> "They created Tibia and all life on it. Visit our library and learn about them." +"life" -> "The gods decorated Tibia with various forms of life. Plants, the citizens, and even the monsters." +"plant" -> "Just walk around. You will see grass, trees, and bushes." +"citizen" -> "Only few people live here. Walk around and talk to them." +"obi" -> "He is a local shop owner." +"al","dee" -> * +"seymour" -> "Seymour is a loyal follower of the king and responsibe for the academy." +"academy" -> "You should visit Seymour in the academy and ask him about a mission." +"willie" -> "He is a fine farmer. The farm is located to the left of the temple." +"monster" -> "They are a constant threat. Learn to fight by hunting rabbits, deers and sheeps. Then try to fight rats, bugs and perhaps spiders." +"help" -> "First you should try to get some gold and buy better equipment." +"hint" -> * +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "You have to slay monsters and take their gold. Or sell food at Willie's farm." +"money" -> "If you need money, you have to slay monsters and take their gold. Look for spiders and rats." +"rat" -> "In the north of this temple you find a sewer grate. Use it to enter the sewers if you feel prepared. Don't forget a torch; you'll need it." +"sewer" -> * +"equipment" -> "First you need some armor and perhaps a better weapon or a shield. A real adventurer needs a rope, a shovel, and a fishing pole, too." +"fight" -> "Take a weapon in your hand, activate your combat mode, and select a target. After a fight you should eat something to heal your wounds." +"slay" -> * +"eat" -> "If you want to heal your wounds you should eat something. Willie sells excellent meals. But if you are very weak, come to me and ask me to heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +"time" -> "Now, it is %T, my child." +} diff --git a/data/npc/clark.npc b/data/npc/clark.npc new file mode 100644 index 0000000..a8c7f5d --- /dev/null +++ b/data/npc/clark.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# clark.npc: Datenbank für den captain der Wache clark + +Name = "Clark" +Outfit = (129,19-79-98-95) +Home = [32636,32796,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye." + +"bye" -> "Goodbye.", Idle +"farewell" -> * +"job" -> "I am the captain of the guards and responsible for upholding law and order in the colony." +"name" -> "I am called Clark." +"guards" -> "The guards of Port Hope are led by Lord Seamus, a nobleman from Thais." +"seamus" -> "He is ill and I am responsible for upholding the law now." +"time" -> "It is %T right now." +"king" -> "Our king can be proud of this colony." +"venore" -> "The Venorans are fine and generous people. It's a good thing that they put all this effort into this colony. I don't know what we would do if it weren't for them." +"thais" -> "Thais is a big city but if we do well enough here, Port Hope might one day rival its size." +"carlin" -> "It's a shame those women still get away with their independence. Friends told me that too many of Thais's resources are wasted in useless projects and the army is too weak to claim back what is ours." +"edron" -> "The knights of Edron are somewhat arrogant. They use the resources of the isle only for themselves instead of sharing it." +"jungle" -> "The jungle is our first and foremost enemy." + +"tibia" -> "I would guess a good part of the world is under Thaian rule." + +"kazordoon" -> "As long as the dwarves stay there and don't expand, I think it's ok to let them have this ugly piece of rock." +"dwarves" -> "Dwarves are impressive fighters. The Venorans talked about hiring some of them as mercenaries to help us out." +"dwarfs" -> * +"ab'dendriel" -> "My guess is that those elves are secretly allied with Carlin. I would not trust an elf if I would meet one." +"elves" -> * +"elfs" -> * +"darama" -> "This continent will be ours one day." +"darashia" -> "A village of harmless cultists. They pose no real threat and could easily been integrated into the Thaian realm." +"ankrahmun" -> "This city is a threat to Thais's domination of Darama. Yet, we are too weak to handle this threat." +"ferumbras" -> "He is the enemy number one to the kingdom and all good people." +"excalibug" -> "I heard the knights of Edron hide it somewhere instead of using it for the good of the country." +"apes" -> "Rest assured, we will handle that problem." +"lizard" -> "They pose no real threat to us." +"dworcs" -> "They fear our power and hide in some caves." +} diff --git a/data/npc/clyde.npc b/data/npc/clyde.npc new file mode 100644 index 0000000..049bd13 --- /dev/null +++ b/data/npc/clyde.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# clyde.npc: Datenbank für den Tavernenbesitzer Clyde + +Name = "Clyde" +Outfit = (128,98-40-48-95) +Home = [32573,32753,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, dear customer." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, visit us again.", Idle +"farewell" -> * +"job" -> "I am your host. I run this tavern." +"name" -> "I am Clyde, your host." +"time" -> "Don't worry about the time. Take a seat, have a drink. Time runs differently in Port Hope." +"king" -> "I wish the king would be a bit more concerned about this colony. I am convinced with a few more resources, we could improve Port Hope a lot." +"venore" -> "It's not always easy to deal with those Venoran tradesmen. I must admit they don't show any interest in my area of business. Just between you and me, my friend, sometimes they give me shivers." +"thais" -> "Sadly, Thais is far away and you will notice that in many places. You'll find out about this yourself, so lets talk about something else please." +"carlin" -> "I know only little about Carlin and here nobody cares about it for sure." +"edron" -> "As I started business here, I was hoping for a second Edron. I have not abandoned my hope though. Afer all, this place is called Port Hope, isn't it?" +"jungle" -> "This forest is not an ordinary forest. It's more like a force of nature, like a river or even a storm." +"tibia" -> "I have seen only little of this world. Probably it should be you telling me about the world, and not the other way around." +"kazordoon" -> "If you want to learn something about Kazordoon you should talk to our local dwarves." +"dwarves" -> "There was a handful of dwarves that came here when the colony was founded. They were looking for treasures and gold as far as I know. After some argument a bunch of them left, they headed into the jungle and were never seen again." +"dwarfs" -> * +"ab'dendriel" -> "Sadly it is next to nothing that I know about the elves and their city." +"elves" -> * +"elfs" -> * +"darama" -> "It's a continent full of extremes. The jungle in the humid east, the desert in the dry west." +"darashia" -> "I was there quite often, using the flying carpet. It's quite different from the other towns I have seen, but surely worth a trip." +"ankrahmun" -> "If I were you I'd stay as far away from this town as I could. It is ruled by an undead abomination and its inhabitants worship death." +"ferumbras" -> "This incarnation of evil seems to concentrate his efforts on Thais and its surroundings, but who knows what comes next into the mind of this madman?" +"excalibug" -> "The rumours I overheard did not mention this continent as one of its hiding places." +"apes" -> "They seem to live in the depth of the jungle in ruins that show the markings of the lizard folk. I wonder if they now try to conquer our city too." +"lizard" -> "The lizards are hostile to us. They probably see no big difference between us and the ape people." +"dworcs" -> "The dworcs live in the south in an underground network consisting of caves. They use poisoned weapons and love to build all kind of traps. You don't want to know the fate of those that have been trapped, believe me." + +"buy" -> "I can offer you bread, cheese, ham, or meat as well as several drinks." +"offer" -> * +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +} diff --git a/data/npc/cobra.npc b/data/npc/cobra.npc new file mode 100644 index 0000000..59063bb --- /dev/null +++ b/data/npc/cobra.npc @@ -0,0 +1,21 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cobra.npc: Datenbank für die steincobra + +Name = "Cobra" +Outfit = (0,2051) +Home = [33366,32855,14] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",QuestValue(270) > 0,Poison>500,! -> "Venture the path of decay!", Teleport(33397,32836,14), EffectOpp(11),SetQuestValue(270,0) +ADDRESS,"hi$",QuestValue(270) > 0,Poison>500,! -> * + +ADDRESS,"hello$",! -> "Begone! Hissssss! You bear not the mark of the cobra!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sssssilence!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hissssssssss." + +} diff --git a/data/npc/cornelia.npc b/data/npc/cornelia.npc new file mode 100644 index 0000000..98fcc25 --- /dev/null +++ b/data/npc/cornelia.npc @@ -0,0 +1,111 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# cornelia.npc: Datenbank für die Rüstungshändlerin Cornelia + +Name = "Cornelia" +Outfit = (139,95-57-102-115) +Home = [32334,31796,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the finest armorshop in the land, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> "One moment please, %N.", Queue +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * +"job" -> "I run this armoury. If you want to proctect your life you'd better buy my wares." +"shop" -> * +"name" -> "My name is Cornelia." +"time" -> "It's %T right now." +"help" -> "I sell and buy armor, helmets, and shields. Only the dwarfs can make better ones." +"dwarfs" -> "The ancient dwarfen clan halls are far to the east from here." +"monster" -> "With my armor you need not fear any monsters!" +"dungeon" -> "While exploring the dungeons of the land you will learn how important a good armor is." +"sewer" -> "Sewers are males' business." +"thanks" -> "You are welcome." +"thank","you" -> "You are welcome." +"ghostlands" -> "THE GHOSTLANDS??? Make sure to buy the best protection in store before you get even close to them." + +"buy" -> "What do you need? I sell armor, helmets, shields, and trousers." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are armor, helmets, trousers, and shields." + +"weapon" -> "Ask Rowenna in the other shop about it." +"helmet" -> "I am selling leather helmets, chain helmets, brass helmets, and viking helmets. What do you want?" +"armor" -> "I am selling leather armor, chain armor, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, brass shields, and plate shields. What do you want?" +"trousers" -> "I am selling chain legs and brass legs. What do you need?" +"legs" -> * + +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "Do you want to buy brass legs for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "Do you want to buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "Do you want to buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "Do you want to buy %A brass legs for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "Do you want to sell plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 + +"sell", %1,1<%1, "leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell", %1,1<%1, "plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell", %1,1<%1, "steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell", %1,1<%1, "plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell", %1,1<%1, "wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell", %1,1<%1, "battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell", %1,1<%1, "brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell", %1,1<%1, "chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/costello.npc b/data/npc/costello.npc new file mode 100644 index 0000000..d5fbd1a --- /dev/null +++ b/data/npc/costello.npc @@ -0,0 +1,102 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# costello.npc: Datenbank für den Abt Costello + +Name = "Costello" +Outfit = (57,0-0-0-0) +Home = [32180,31936,7] +Radius = 3 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(220)>0,! -> "WHAT? You have to be that trespasser my brothers told me about! Entering the restricted area is a horrible crime!",Topic=3 +ADDRESS,"hi$",QuestValue(220)>0,! -> * + + +ADDRESS,"hello$",! -> "Welcome, %N! Feel free to tell me what has brought you here." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(220)>0,! -> "WHAT? You have to be that trespasser my brothers told me about! Entering the restricted area is a horrible crime!",Idle +BUSY,"hi$",QuestValue(220)>0,! -> * + +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",QuestValue(220)>0 -> "I won't waste my healing powers on you, spawn of evil!", Idle +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking that bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +topic=3,"crime",Level<20,! -> "The only way to redeem such an offense is the sacrifice of 500 gold pieces! Are you willing to pay that sum?",Price=500,Topic=4 +topic=3,"absolution",Level<20,! -> * +topic=3,"crime",Level<40,! -> "The only way to redeem such an offense is the sacrifice of 1.000 gold pieces! Are you willing to pay that sum?",Price=1000,Topic=4 +topic=3,"absolution",Level<40,! -> * +topic=3,"crime",Level<60,! -> "The only way to redeem such an offense is the sacrifice of 5.000 gold pieces! Are you willing to pay that sum?",Price=5000,Topic=4 +topic=3,"absolution",Level<60,! -> * +topic=3,"crime",Level>59,! -> "The only way to redeem such an offense is the sacrifice of 10.000 gold pieces! Are you willing to pay that sum?",Price=10000,Topic=4 +topic=3,"absolution",Level>59,! -> * +topic=3,! -> "Be gone!", Idle + +Topic=4,"yes",CountMoney "Begone! You do not have enough money!", Idle +Topic=4,"yes",! -> "So receive your absolution! And never do such a thing again!", DeleteMoney, EffectOpp(13),SetQuestValue(220,0) +Topic=4,! -> "Then be gone!", Idle + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am the abbot of the white raven monastery on the isle of the kings." +"name" -> "My name is Costello." +"tibia" -> "That is the name of our world and its major continent." +"god" -> "They created Tibia and all life on it." +"life" -> "On Tibia there are many forms of life. Plants, the citizens, and monsters." +"plant" -> "Just walk around, you will see grass, trees, and bushes." +"white","raven" -> "The legends tell us of a white raven which lead the ship of the first monk of our order here. He discovered this isle and the caves beneath it." +"caves" -> "Anselm, the first of our order, discovered them while looking for a suitable burial place for his king." +"anselm" -> "He was a humble and pious man, and he was chosen by the royal family of thais to find a resting place for their dead." +"isle" -> "We founded our monastery to guard the royal tombs and to gather wisdom and knowledge." +"order" -> * +"wisdom" -> "You are allowed to enter the library upstairs. Stay there and don't go upstairs, because that area is reserved for members of our order." +"knowledge" -> * +"tibianus" -> "One day every Tibianus ends up here." +"king" -> "The bygone leaders of the Thaian empire rest beneath this monastery in tombs and crypts." +"tomb",QuestValue(63)<1 -> "The tombs and crypts of the Thaian lineage are well protected deep beneath our abbey, although ... but surely this will not interest you." +"crypts",QuestValue(63)<1 -> * +"although",QuestValue(63)<1 -> "In my dreams the dead are talking to me about torment and disturbance. But I might be imagining things." +"interest",QuestValue(63)<1 -> * +"imagining",QuestValue(63)<1 -> "Brother Fugio, the only one of our order who is allowed to enter the crypts, assures me everything is all right." +"torment",QuestValue(63)<1 -> * +"disturbance",QuestValue(63)<1 -> * + +"fugio",QuestValue(63)<1 -> "To be honest, I fear the omen in my dreams may be true. Perhaps Fugio is unable to see the danger down there. Perhaps ... you are willing to investigate this matter?", topic=1 +"alright",QuestValue(63)<1 -> * +Topic=1,"yes" -> "Thank you very much! From now on you may open the warded doors to the catacombs", SetQuestValue(63,1), SetQuestValue(64,1) + +# 63=forschen 64=questtür wg. eingang von unten + +Topic=1,"no" -> "Please forgive an old man, I shouldn't have asked a stranger anyways." +Topic=1 -> * + + +"diary",QuestValue(219)=0 -> "Do you want me to inspect a diary?",Type=3212, Amount=1,topic=2 +"diary",QuestValue(219)>0 -> "Thank you again for handing me that diary." + +Topic=2,"yes",Count(Type)>=Amount -> "By the gods! This is brother Fugio's handwriting and what I read is horrible indeed! You have done our order a great favour by giving this diary to me! Take this blessed Ankh. May it protect you in even your darkest hours.", Delete(Type), Create(3214),SetQuestValue(219,2) +Topic=2,"no" -> "Uhm, as you wish." +Topic=2 -> * + +"passage",QuestValue(63)=1,QuestValue(64)=1 -> "Oh of course, I will order Jack and the fisher Windtrouser to give you transportation if needed.", SetQuestValue(62,1) +"passage" -> "You should not be here at all and I won't allow anyone to transport you from or to this isle." + +"ferumbras" -> "Don't mention this servant of evil here." +"excalibug" -> "Sadly we have only little knowledge on this topic." +"news" -> "Sorry, we rarely hear anything new here." +"monster" -> "There are really too many of them in Tibia. But who are we to question the wisdom of the gods?" + +"heal$",QuestValue(220)>0 -> "I won't waste my healing powers on you, spawn of evil!", Idle +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking that bad. Sorry, I can't help you." + +} diff --git a/data/npc/crone.npc b/data/npc/crone.npc new file mode 100644 index 0000000..911dbc2 --- /dev/null +++ b/data/npc/crone.npc @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# crone.npc: Bansheevettel in Vashresamuns Grabmal + +Name = "The Crone" +Outfit = (78,0-0-0-0) +Home = [33229,32522,14] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N... mortal" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Patience, mortal %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I don't remember my name, neither my days as a mortal." +"job" -> "Once I was Vashresamun's favourite handmaiden. But I have fallen from her grace and now I am exiled from her tomb." +"grace" -> "Do not ask about that, mortal. Memories bring too much grief." +"fallen" -> * +"exiled" -> * + +"vashresamun" -> "I mourn the dark day I was exiled from her tomb." +"tomb" -> "Her tomb is sealed and can only be entered by a certain melody." +"melody" -> "Vashresamun erased the memory of the tune from my mind, I only remember its name: the secret of the rose garden." +} diff --git a/data/npc/dabui.npc b/data/npc/dabui.npc new file mode 100644 index 0000000..00cf59a --- /dev/null +++ b/data/npc/dabui.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dabui.npc: Datenbank für die Stadtwache Dabui in Darashia + +Name = "Dabui" +Outfit = (129,95-10-26-76) +Home = [33229,32409,7] +Radius = 1 + +Behaviour = { +@"guards-darama.ndb" +} diff --git a/data/npc/dagomir.npc b/data/npc/dagomir.npc new file mode 100644 index 0000000..3788a9e --- /dev/null +++ b/data/npc/dagomir.npc @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dagomir.npc: Datenbank für den "Bankier" Dagomir + +Name = "Dagomir" +Outfit = (130,0-2-41-76) +Home = [33018,32047,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Wha... what?? HOW DARE YOU!!?? LEAVE ME ALONE ON MY TOILET AT ONCE!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +} diff --git a/data/npc/dallheim.npc b/data/npc/dallheim.npc new file mode 100644 index 0000000..fc9e7d5 --- /dev/null +++ b/data/npc/dallheim.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dallheim.npc: Datenbank für den Dorfwächter Dallheim (Rookgaard) + +Name = "Dallheim" +Outfit = (131,76-38-76-95) +Home = [32093,32180,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not now." +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye.", Idle +"farewell" -> * +"how","are","you" -> "Fine." +"sell" -> "I sell nothing." +"advice",level<4 -> "Be careful out there and avoid the dungeons." +"advice",level>3 -> "Be careful out there." +"job" -> "I am the bridgeguard. I defend Rookgaard against the beasts from the wilderness and the dungeons!" +"name" -> "Dallheim." +"time" -> "No idea." +"help" -> "I have to stay here, sorry, but I can heal you if you are wounded." +"monster" -> "I will crush all monsters who dare to attack our base." +"dungeon" -> "Dungeons are dangerous, be prepared." +"wilderness" -> "There are wolves, bears, snakes, deers, and spiders. You can find some dungeon entrances there, too." +"sewer" -> "In the sewers there are some rats, fine targets for young heroes." +"god" -> "I am a follower of Banor." +"banor" -> "The great one! Read books to learn about him." +"king" -> "HAIL TO THE KING!" +"seymour" -> "Leave me alone with this whimp." +"willie" -> "A fine cook and farmer he is." +"amber" -> "I don't trust her." +"hyacinth" -> "Strange Fellow, hides somewhere in the mountains of the isle." +"weapon" -> "With my spikesword I slice even a cyclops in pieces." +"magic" -> "Not interested in such party tricks." +"tibia" -> "A nice place for a hero, but nothing for whelps." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +} diff --git a/data/npc/dane.npc b/data/npc/dane.npc new file mode 100644 index 0000000..1380d3e --- /dev/null +++ b/data/npc/dane.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dane.npc: Datenbank für die Wirtin Dane + +Name = "Dane" +Outfit = (136,79-58-86-96) +Home = [32308,31838,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the wave cellar, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back from time to time." + +"bye" -> "Please come back from time to time.", Idle +"farewell" -> * +"job" -> "I am the owner of this place of relaxation." +"saloon" -> * +"cellar" -> "It's pretty, isn't it?" +"name" -> "I am Dane." +"time" -> "It is exactly %T." +"news" -> "I heard nothing interesting lately." + +"offer" -> "I can offer you milk, water, and lemonade." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Sorry, we just sell drinks." +"alcohol" -> "Alcohol makes people too aggressive. We don't need such stuff in Carlin." +"wine" -> * +"beer" -> * + +"lemonade" -> Type=2875, Data=12, Amount=1, Price=5, "Do you want to buy a bottle of refreshing lemonade for %P gold?", Topic=1 +"milk" -> Type=2875, Data=9, Amount=1, Price=4, "Do you want to buy a bottle of our revitalizing milk for %P gold?", Topic=1 +"water" -> Type=2875, Data=1, Amount=1, Price=2, "Do you want to buy a bottle of crystal clear water for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2875, Data=12, Amount=%1, Price=5*%1, "Do you want to buy %A bottles of refreshing lemonade for %P gold?", Topic=1 +%1,1<%1,"milk" -> Type=2875, Data=9, Amount=%1, Price=4*%1, "Do you want to buy %A bottles of our revitalizing milk for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2875, Data=1, Amount=%1, Price=2*%1, "Do you want to buy %A bottles of crystal clear water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/daniel.npc b/data/npc/daniel.npc new file mode 100644 index 0000000..7caea79 --- /dev/null +++ b/data/npc/daniel.npc @@ -0,0 +1,90 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# daniel.npc: Datenbank für Gouverneur Daniel Steelsoul + +Name = "Daniel Steelsoul" +Outfit = (73,0-0-0-0) +Home = [33191,31795,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings and Banor with you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,"salutations$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up! I am talking already!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "PRAISE TO BANOR!" + +"bye" -> "PRAISE TO BANOR!", Idle +"news" -> "Nothing new from the north." +"king" -> "LONG LIVE THE KING!" +"leader" -> "King Tibianus III is our wise and just leader." +"name" -> "I am Sir Daniel Steelsoul of the Sacred Order of Banor's Blood." +"job" -> "I am the governor of this isle, Edron, and grandmaster of the Knights of Banor's Blood." +"how","are","you"-> "I did not have much sleep lately, but I am fine." +"sell" -> "Are you suggesting I am corruptible?", Topic=2 +"army" -> "The army protects the Thaian realm. The order of the Knights of Banor's Blood supports them with all our skills." +"guard" -> * +"general" -> "Harkath Bloodblade declined the governorship because he's needed more in Thais." +"enemies" -> "Evil challenges the forces of good in any shape. Be it the claws of vicious monsters or the seductive dark secrets of rotten power." +"enemy" -> * +"banors","blood" -> "We believe that the blood of Banor runs through the veins of all humans. Therefore, we are responsible to live up to Banors standards and not to stain his legacy with sinful acts." +"castle" -> "The castle was built on elder foundations we found on this isle." + +"edron" -> "This isle is rumoured to have been the home of a powerful ancient race which became extinct before the corpsewars. It was up to King Tibianus III to reclaim it for humanity and to found this colony." +"colony" -> "With the Thaian army bound to other duties, our order was entrusted to secure the area. We defeated the evil minotaurs living right here and cleansed the isle of their unholy presence." +"minotaur" -> "The minotaurs, though evil, were worthy opponents. After the treason of the man who is now known as Kaine Kinslayer, we lack the manpower to crush their cyclopean allies, too." +"cyclop" -> "They live in an underground city, known as cyclopolis in the north of the isle. Constantly forging weapons for the servants of darkness." +"allies" -> * +"kaine" -> "He was my second in command. After learning about the forbidden ruins, he, the priestess Agaltha, and their followers freed the criminals we brought here as workers and headed to the north." +"ruins" -> "An ancient taboo forbids to enter the northern ruins." +"taboo" -> "We thought it was only superstition and no one bothered that Kaine and his friends went there to hunt servants of evil that might have hidden there. So we did not notice the dark cult they started." +"cult" -> "We know only little about them. Kaine and his fallen knights were joined by criminal scum and remaining forces of darkness that escaped us. They were joined by some ominous dark monks." +"monk" -> "We don't know if they came here or already hid in the ruins as we arrived. Maybe they seduced Kaine or Agaltha, maybe they were theirs for years." +"agaltha" -> "She was beautiful but seemed cold hearted. She spoke little to me, prefered the company of Kaine." +"eremo" -> "Eremo is a very wise man. I visit him sometimes on his little island near Edron. Just ask a fisherman for a passage." +"fisherman" -> "Pemaret is a fisherman on Cormaya." +"cormaya" -> "It is a peaceful isle next to Edron with a nice village. There, you should visit the wonderful garden." +"falk" -> "A promising young fellow." +"horn","plenty" -> "I hardly find the time to visit the tavern." +"mirabell" -> * +"willard" -> "When he was young, Willard served in the royal army." +"weapon" -> "Look for Willard, our local blacksmith." +"armor" -> * +"academy" -> "After the treason of Kaine, we observe these mages closely. If even a knight falls prey to the seduction of the forbidden ruins, no one can tell how easy some of these mystics might betray his people." +"amanda" -> "I think this nun might be a bit young for this position, but I won't question the decisions of the church of Banor's Blood." +"benjamin" -> "He and his men fought against Ferumbras somewhere in the north of this isle, long before there were even plans of a colony. Only old Ben returned alive from the battle, but his mind was broken." +"ferumbras" -> "He searched something in the north of the isle years ago. Probably he needed something from the forbidden ruins. He was chased and fought by the troops of General Benjamin." +"join" -> "You may join the order of Banor's blood if you prove your honor." +"honor" -> "Only those who live a life of bravery, honor, and piety may join our sacred order." +"piety" -> * +"bravery" -> * +"quest" -> "A life in bravery, honor, and piety should be every man's most important quest." +"mission" -> * +"god" -> "I worship Banor, the first champion of good!" +"banor" -> "His spirit and blood are within us. Honor this fact or be cast into hell." +"zathroth" -> "Do not mention the name of the cursed one!", Burning(10,1), EffectOpp(5), EffectMe(8) +"brog" -> "The rotten cyclopses whorship the raging giant of hell." +"monster" -> "We cleansed the south of any major enemy, but watch out while travelling the north." +"excalibug" -> "With this weapon in my hand, I would teach the servants of darkness the true meaning of the word fear." +"kazordoon" -> "Now and then a dwarf comes to this isle. Most behave secretive about their reason to come here. As far as I can tell they are looking for some dwarfish artifact which was lost in ancient times." +"dwarf" -> * +"carlin" -> "I belong to a sacred order and don't bother about mundane politics." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) + +Topic=2,"yes" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +Topic=2 -> "Then be more careful with your words!" +} diff --git a/data/npc/dario.npc b/data/npc/dario.npc new file mode 100644 index 0000000..e032a44 --- /dev/null +++ b/data/npc/dario.npc @@ -0,0 +1,138 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dario.npc: Datenbank fuer den elfen dario in der arena (ankrahmun) + +Name = "Dario" +Outfit = (144,3-58-41-115) +Home = [33156,32810,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "In a few moment I will have my attention %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * + +"job" -> "I am the master archer of the arena. I train distance fighters and sell them equipment." +"name" -> "I am Dario of Ab'Dendriel." +"time" -> "Time is unimportant to me." +"temple" -> "The temple is somewhere south at the coast." +"arkhothep" -> "The pharaoh seems to be mighty beyond imagination." +"ashmunrah" -> "There was some fighting long ago. The old pharaoh lost his power to his son Arkhothep." +"scarab" -> "Scarabs are dangerous. They are quick, resistant to poison and theis shells are hard as steel." +"tibia" -> "I travel a lot to see everything. For now I settle here for some time." +"carlin" -> "I was there some time ago. It was lovely and reminded me of my home Ab'Dendriel." +"thais" -> "Thais is too crowded for my taste." +"edron" -> "I think Edron is quite typical for a human settlement." +"venore" -> "I did not like the greedy attitude of the people there." +"kazordoon" -> "The small people are too hectic and greedy. They don't understand the harmony of nature." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Sometimes I miss my brethren and sisters. But for now I want to see the world and travel around." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is hard and challenging. I like challenges." +"darashia" -> "The city seemed a bit dull and peacefull to me, so I left for Ankrahmun." +"daraman" -> "You should ask about him in Darashia. People there talked a lot about him." +"ankrahmun" -> "Ankrahmun is unlike any other city I've seen. Sometimes it gives me shivers ... on the other hand it makes me stay on guard and feel alive, despite the undeath cult." + +"ascension" -> "I don't care for this human concepts." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * +"undead" -> "I don't understand this cult yet. Just ask around and people will tell you." +"undeath" -> * +"arena" -> "People who fight here do it on their own choice. So I don't care." +"palace" -> "Under the palace are crypts, full of minor undead and creatures that have failed the pharaoh. He allows everyone to slay them as they see it fit." + +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Asha Thrazi.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=4 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=4 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=4 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=4 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=4 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=4 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=4 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=4 + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + + +} diff --git a/data/npc/demongrd.npc b/data/npc/demongrd.npc new file mode 100644 index 0000000..48a5b6a --- /dev/null +++ b/data/npc/demongrd.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# demongrd.npc: Datenbank für den Demonguard + +Name = "Demonguard" +Outfit = (131,113-113-94-113) +Home = [32854,32318,10] +Radius = 0 + +Behaviour = { +-> "Die, intruder!", Burning(25,8), EffectOpp(16), EffectMe(14), Idle +} diff --git a/data/npc/demonskeleton.npc b/data/npc/demonskeleton.npc new file mode 100644 index 0000000..33784dc --- /dev/null +++ b/data/npc/demonskeleton.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# demonskeleton.npc: Datenbank fuer ein NPC-Daemonenskelett + +Name = "Demon Skeleton" +Outfit = (37,0-0-0-0) +Home = [32666,31676,15] +Radius = 0 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/data/npc/dermot.npc b/data/npc/dermot.npc new file mode 100644 index 0000000..d7caa46 --- /dev/null +++ b/data/npc/dermot.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dermot.npc: Datenbank für den Ortsvorsteher Dermot auf der Insel Fibula + +Name = "Dermot" +Outfit = (129,76-49-19-95) +Home = [32165,32437,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, traveller %N. How can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I cannot talk to two persons at the same time. You'll have to wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "See you again." + +"bye" -> "See you again.", Idle +"farewell" -> * +"job" -> "I am the magistrate of this isle." +"equipment" -> "I am not selling equipment. You'll have to visit Timur." +"timur" -> "He is the salesman in this village. " +"name" -> "I am Dermot, the magistrate of this isle." +"time" -> "Time is not important on Fibula." +"dermot" -> "I am the magistrate of this isle." +"magistrate" -> "Thats me." +"fibula" -> "You are at Fibula. This isle is not very dangerous. Just the wolves bother outside the village." +"wolf" -> "There are a lot of wolves outside the townwall. They disturb our farmers." +"farmer" -> "The inhabitants of Fibula live on fishing, farming, and hunting." + +QuestValue(231)=1,"present" -> Type=3218, Amount=1,"You have a present for me?? Really?",Topic=2 +"present" -> "I don't understand what you are talking about." +Topic=2,"yes",Count(Type)>=Amount -> "Thank you very much!",Delete(Type),SetQuestValue(231,2) +Topic=2,"yes" -> "What? There is no present, at least none for me! Stop this foolish jokes!",Idle +Topic=2 -> "Hmm, maybe next time." + +"dungeon" -> "Oh, my god. In the dungeon of Fibula are a lot of monsters. That's why we have sealed it with a solid door." +"sewer" -> * +"monster" -> * +"entrance" -> "The entrance is near here." +"key" -> Type=2968, Data=3940, Amount=1, Price=2000, "Do you want to buy the dungeon key for %P gold?", Topic=1 +"door" -> * + +Topic=1,"yes",CountMoney>=Price -> "Now you own the key to the dungeon.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You've not enough money to buy the key." +Topic=1 -> "Hmm, maybe next time." +} diff --git a/data/npc/digger.npc b/data/npc/digger.npc new file mode 100644 index 0000000..c20ec0f --- /dev/null +++ b/data/npc/digger.npc @@ -0,0 +1,53 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# digger.npc: Datenbank für den Magieladen-Verkäufer Digger + +Name = "Digger" +Outfit = (9,0-0-0-0) +Home = [32970,32087,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, %N is that you? You look inconveniently healthy." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, your time will finally come.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"name" -> "They call me Digger, that fine with me." +"frans" -> "I think the FRANS is bugged." +"digger" -> "So what?" +"job" -> "I am selling some potions." +"sorcerer" -> "The way of the magicwielder is the only way to true power." +"druid" -> * +"magic" -> "This is the magic market. Just have a look around." +"market" -> * +"vladruc" -> "Better don't cross the master!" +"urghain" -> * +"ferumbras" -> "An upstart of minor skills and great ambitions." +"excalibug" -> "Just a knights' legend." + +"offer" -> "You may be interested in my life and mana fluids." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"potion" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2 -> "Don't overestimate my patience." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3 -> "Don't overestimate my patience." +} diff --git a/data/npc/dixi.npc b/data/npc/dixi.npc new file mode 100644 index 0000000..b84b3aa --- /dev/null +++ b/data/npc/dixi.npc @@ -0,0 +1,161 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dixi.npc: Datenbank für Obi's Angestellte Dixi + +Name = "Dixi" +Outfit = (136,96-99-76-115) +Home = [32105,32207,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Hello, Sir. How may I help you, %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Hello, Mam. How may I help you, %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N. I'll be with you in a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye", male -> "Good bye, Sir.", Idle +"farewell", male -> * +"bye", female -> "Good bye, Mam.", Idle +"farewell", male -> * +"how","are","you" -> "I am fine, thank you." +"sell" -> "We're selling many things. Please have a look at the blackboards downstairs to see a list of our inventory." +"job" -> "I'm helping my grandfather Obi with this shop. Do you want to buy or sell anything?" +"name" -> "I'm Dixi." +"time" -> "It is %T." +"help" -> "If you need something, please let me know." +"stuff" -> "We sell equipment of all kinds. Please let me know if you need something." + +"wares" -> "We sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "We sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "We sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "We sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "We sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "We sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? We sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "I am sorry, an agent of Al Dee bought all our picks. Now he has a monopoly on them." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"sell","club" -> "I'm sorry, we don't buy this." +"sell","dagger" -> Type=3267, Amount=1, Price=2, "I can give you %P gold for this, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "I can give you %P gold for this, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/djema.npc b/data/npc/djema.npc new file mode 100644 index 0000000..ae90265 --- /dev/null +++ b/data/npc/djema.npc @@ -0,0 +1,86 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# djema.npc: Datenbank für die Bibliothekarin Djema + +Name = "Djema" +Outfit = (136,77-9-86-131) +Home = [33101,32520,3] +Radius = 2 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Wow! A human? Here? Hey? Where do you come from, %N? Oh, I'm so excited!" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Another human?! Please don't go away! Stay! Please wait a minute, %N!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Please don't go!" + +"bye" -> "Aww - you really have to leave so soon? You must come and visit me again. Please - promise me!", Idle +"farewell" -> * +"name" -> "My name is Djema. Daddy says it means 'Moonflower' in the old language." +"djem" -> "That is my name. I do not like it much, though. Everybody around here calls me Djem." +"job" -> "I am the librarian in this place. I don't like the work too much because we do not really have that many books, and most of them are written by people who have died thousands of years ago. ...", + "If dad wanted me to have a job to keep me entertained that was a real non-starter." +"librarian" -> "Yes. I administrate the library. You know - registering new books, sorting them in alphabetical order etc. ...", + "To be honest I am not very good in this. Thank goodness daddy gave that magical blackboard to me. It is quite useful." +"gabel" -> "Gabel is my father. He runs this place. Of course, he is not my real daddy. But he adopted me, you know. Or rather, all the Marid adopted me." +"father" -> * +"dad" -> * +"parents" -> "I can't remember them. I... They have both died a long time. At least that is what I have been told. Listen, can we talk about something else?" +"marid" -> "The djinn you have met call themselves the Marid. They are generally very nice. Nice, but boring." +"djinn" -> "The djinn are a curious race. They are nice, but they are always so serious. Oh, don't get me wrong, there is not a single djinn around here I do not like, but, you know, they are not much fun. ...", + "I guess that is because they are all such devout followers of Daraman, but perhaps it is just because of all the bad things that have happened." +"daraman" -> "Daraman was a human, but he must have been something very special - he was a holy man. To this day daddy and all the other djinn around here look up to Daraman as a true prophet." + +"king" -> "Officially there is no king of the djinn. Daddy used to hold the title, but he has chosen to put if off. Of course, he is still the undisputed leader of the Marid. He simply dislikes the title." +"efreet" -> "Apparently the greenskins are different from the Marid who have raised me. I don't know. Perhaps the Efreet would be more fun than the djinn around here, but then daddy says they are really evil." +"malor" -> "Malor is the leader of the Efreet. I have never seen him, but they say he is really nasty. Daddy always gets upset when this name is pronounced." +"mal'ouquah" -> "Oh, that place. They say it is pure evil. But I don't think it looks that evil. I have seen the dark fortress once, you know?!" +"dark", "fortress" -> "One night I went there. I wanted to see it for myself. Don't tell daddy, though. He would freak out if he heard I was there." +"ashta'daramai" -> "That's what this place is called. Sure, it is beautiful, but it is as also boring and sometimes downright depressing. Sometimes I feel like I am bound to this place by golden chains." +"human" -> "I have lived here for as long as I can remember, but I know I don't belong here. I belong to them! I am a human! One day I will leave this place, and I will never come back." +"zathroth" -> "Yes, I have heard his name before. Let's see... Yes, I have read his name in a book! It was a book about gods and creation. Pretty weird stuff." +"tibia" -> "Daddy has often told me about how huge and mysterious this world is. How much I would like to see it all. But he won't let me go. ...", + "I have read books about the northern continent inhabited by thousands and thousands of people. Ordinary humans just like you and me! Imagine that!" +"darashia" -> "Darashia is a beautiful city to the north. I have been there! One day Daddy disguised himself, and he took me there. It was awesome. ...", + "There was so much life and colour and excitement... I suppose he did it because he knew how much I yearned to go there. Bless him - he only wanted to help. But after that I felt worse than ever." +"edron" -> "I have read all about the northern cities. It is almost as if I had been there myself." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "If there is one city I don't want to see it is Ankrahmun. I have heard all kinds of stories about the pharaoh and his cult of weirdos!" +"scarab" -> "Ah, those nasty critters. They give me the creeps!" + +"pharaoh" -> "Apparently he is an undead! Yuk - how disgusting!" +"palace" -> "I do not care just how beautiful the pharaoh's palace is. I will never go there. The minute I would see some undead pile of flesh I would dash for the door screaming." +"ascension" -> "I have heard that term before. Has to do with the pharaoh's cult, I think, but I do not know for sure, and I'm not particularly eager to learn more about it." +"akh'rah" -> "Hm. No - doesn't ring a bell." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "I don't know whether I should hate or love these mountains. I mean, they are so beautiful. If only you saw those peaks in the evening, when the sun is setting. It is like a thousand fires that set the horizon aglow. ...", + "But... ah, I don't know. I think I would give it all away if I could live somewhere among my own kind." +"kha'labal" -> "That is the huge desert to the east. You can see it from here if you look in the direction of the rising sun. It seems huge... endless... It makes my heart sink whenever I watch it." +"war" -> "Daddy and all the other djinn around here are so restive lately. I know they try not to show it, but I can sense that they are tense and perhaps even a bit afraid. ...", + "To be honest I am sick of being patronised. I am a grown-up woman now, and I don't need anybody else's protection anymore!" +"melchior" -> "Hm. I think I have heard that name before. A human, wasn't it? I think he used to drop quite often when I was much younger, but I have no clear memory of him. ...", + "I think he has not visited Ashta'Daramai for a long time." +"alesar" -> "I have never known this djinn, but apparently there is some sad story behind this. Daddy is very sad that Alesar left us. Of course, he tries not to show, but as usual he does a bad job about it." +"lamp" -> "When I was still a kid I could not understand how it could be that I was not able to sleep in a lamp. ...", + "That was when I wanted nothing more than to be like the people around me. I may see things a bit more clearly now, but the feeling of yearning remains." +"fa'hradin" -> "Uncle Fad is a weird guy. He is incredibly intelligent, but he is also totally inept in worldly matters. Sometimes I feel he is not quite at home in this world." +"fa'hradin","lamp" -> "I have heard many a story about this artifact. It was used to trap that Malor guy. Clever idea of good old uncle Fa'hradin." +"book" -> "The books around here are not exactly what I would call a riveting read. Most of them are technical documents written by uncle Fad at some point or other. Now and then he turns up and brings new files. Not that anybody would ever read them. ...", + "It is my job to make sure they are filed and registered." +#"blackboard" -> "Yes! All books we have are listed in it! The whole library! And the best thing is that whenever a new book is added or one is removed the list is updated by magic. Isn't that great! Take a look!" +#"magical","blackboard" -> * +#"spy" -> "Yes! There was a spy! I have seen him! He was over there, right next to the door. When I came in he just slipped out. It was an Efreet! His green skin gave him away!" +#"fr 0457", QuestValue (###)=### -> "fr 0457? Missing? Let me see? Hey, you are right. It is not on the list. Could it be?","Daraman have mercy! You're right! The file is gone! The spy must have stolen it. Oh dear. What am I to do now?","Quick! Go and tell uncle Fa'hradin. He will know what to do!", SetQuestValue (###)=### +} diff --git a/data/npc/don.npc b/data/npc/don.npc new file mode 100644 index 0000000..f84ca08 --- /dev/null +++ b/data/npc/don.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# don.npc: Datenbank fuer den Bauern Donald McRonald + +Name = "Donald McRonald" +Outfit = (128,41-94-79-76) +Home = [32391,32229,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Hello, Druid %N!" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",! -> "Hmmm, well, hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hmm, I'm busy %N." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "?" + +"bye",Druid -> "May Crunor bless you, Druid %N!", Idle +"farewell",Druid-> * +"job",Druid -> "My wife and I run this farm as good as we can." +"wife",Druid -> "Sherry is my beloved wife." +"donald",Druid -> "I was named Donald, like my grandfather." +"farm",Druid -> "It's a hard but rewarding task to run this farm." +"name",Druid -> "My name is Donald McRonald, noble druid." +"time",Druid -> "Unfortunately I can't help you with that, noble druid." + +"bye" -> "Yes, bye!", Idle +"farewell"-> "Yes, farewell!", Idle +"job" -> "I run a farm, what else?!" +"wife" -> "Sherry is my wife." +"donald" -> "I am Donald." +"farm" -> "It is my farm, yes." +"name" -> "Donald McRonald." +"time" -> "Who cares?" +"weather" -> "Weather is good enough to work on the fields." +"crops" -> "It is hard to grow but worth the effort." +"field" -> "My fields are enchanted by the druids and the wheat grows very quickly." + +"city" -> "The city is to the north." +"crops" -> "I take care of our crops" +"mill" -> "I somtimes have to bring the wheat there." +"spooked" -> "I dont know." +"king" -> "King Tibianus is our king." +"frodo" -> "Frodo? He is a friend of mine." +"oswald" -> "He ignores us and we ignore him." +"bloodblade" -> "A general in the army." +"muriel", sorcerer -> "I dont trust sorcerers like you." +"muriel" -> "I dont trust sorcerers." +"elane" -> "Too noble to care about us." +"gregor" -> "Knights always feel superior to us farmers." +"gregor", knight -> "Knights like you always feel superior to us farmers." +"marvik" -> "Druids are a great help for us, they know much about nature." +"marvik", druid -> "Druids like you are a great help for us, they know much about nature." +"gorn" -> "Hardly know him." +"sam" -> "A blacksmith, eh?" +"quentin" -> "A generous person." +"lynda" -> "She has a good soul." +"spider" -> Type=3988, Amount=1, Price=2, "I will give you %P gold for every spider you bring me. But not a rotten spider that was already dead for some time. Do you have any with you?", Topic=2 + +"buy" -> "I can offer you wheat, cheese, carrots, and corncobs." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have wheat, cheese, carrots, and corn to sell. If you want to sell bread, talk to my wife, Sherry." +"bread" -> "If you want to sell bread, talk to my wife, Sherry." + +"wheat" -> Type=3605, Amount=1, Price=1, "Do you want to buy wheat for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=1 +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=1 + + +%1,1<%1,"wheat" -> Type=3605, Amount=%1, Price=1*%1, "Do you want to buy %A wheat for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=1 + + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You have no spider that died recently." +Topic=2 -> "Hmpf." +} diff --git a/data/npc/dove.npc b/data/npc/dove.npc new file mode 100644 index 0000000..1c155c2 --- /dev/null +++ b/data/npc/dove.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dove.npc: Datenbank für die Postbeamtin Dove + +Name = "Dove" +Outfit = (136,59-86-106-115) +Home = [32919,32075,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, noble %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back soon, noble %N." + +"bye" -> "Come back soon, noble %N.", Idle +"farewell" -> * + +"kevin" -> "Mr. Postner is one of the most honorable men I know." +"postner" -> * +"postmasters","guild" -> "As long as everyone lives up to our standarts our guild will be fine." +"join" -> "We are always looking able recruits. Just speak to Mr.Postner in our headquarter." +"headquarter" -> "Its easy to be found. Its on the road from Thais to Kazordoon and Ab'dendriel." + + +"measurements",QuestValue(234)>0,QuestValue(238)<1 -> "Oh no! I knew that day would come! I am slightly above the allowed weight and if you can't supply me with some grapes to slim down I will get fired. Do you happen to have some grapes with you?",Type=3592, Amount=1,Topic=5 + +"grapes",QuestValue(234)>0,QuestValue(238)<1 -> "Do you happen to have some grapes with you?",Type=3592, Amount=1,Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Oh thank you! Thank you so much! So listen ... ", Delete(Type),SetQuestValue(234,QuestValue(234)+1),SetQuestValue(238,1) +Topic=5,"yes" -> "Don't tease me! You don't have any." +Topic=5 -> "Oh, no! I might loose my job." + + +"job" -> "I am responsible for this post office. If you have questions about the mail system or the depots, just ask me." +"name" -> "My name is Dove." +"dove" -> "Yes, like the bird. " +"time" -> "Now it's %T." +#"mail" -> "The Tibian mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +#"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all Tibian citizens." +"king" -> "Even the king can be reached by the mailsystem." +"tibianus" -> * +"army" -> "The soldiers get a lot of letters and parcels from Thais each week." +"ferumbras" -> "Try to contact him by mail." +"general" -> * +"sam" -> "Ham? No thanks, I ate fish already." +"excalibug" -> "If i find it in an undeliverable parcel, I will contact you." +"news" -> "Well, there are rumours about the swampelves and the amazons, as usual." +"thais" -> "All cities are covered by our mail system." +"carlin" -> * +"swampelves" -> "They live somewhere in the swamp and usually stay out of our city. Only now and then some of them dare to interfere with us." +"amazon" -> "These women are renegades from Carlin, and one of their hidden villages or hideouts might be in the swamp." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/duria.npc b/data/npc/duria.npc new file mode 100644 index 0000000..7566779 --- /dev/null +++ b/data/npc/duria.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# duria.npc: Datenbank fuer die Ritterin Duria + +Name = "Duria" +Outfit = (70,0-0-0-0) +Home = [32617,31938,8] +Radius = 3 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Hiho, fellow knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,Knight,"hiho$",! -> * +ADDRESS,"hello$",! -> "Hiho, visitor %N. Whatdoyouwant?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Waitaminute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Becarefulloutthere, jawoll." + +"bye" -> "Goodbye.",Idle +"farewell" -> * +"job" -> "Iam the Highknight of the dwarfs." +"name" -> "I am Duria Steelbender, daughter of Fire, of the Dragoneaters." +"time" -> "Dunno." +"hero" -> "Heroes are rare in this days, jawoll." +"tibia" -> "Bah, to much plantsandstuff, to few tunnels ifyoudaskme." +"thais" -> "Was there once. Can't handle the crime overthere." +"knight" -> "Knights are proud of being dwarfs, jawoll." +"vocation" -> "Vocation, vocation, wouldratherlike a vacation." +"spellbook" -> "Sellingno spellbooks here. Do I look like a sorc?" +Knight,"spell" -> "Can teach ye healing spells and support spells. What kind of spell you like? Or for which level you want a spell?", Topic=2 +"spell" -> "Sorry, selling spells only to knights, jawoll." + +Knight,"instant","spell" -> "Can teach ye healing spells and support spells. What kind of spell you like?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level you want a spell?", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Goodbye.",Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "Youknowthatspell." +Topic=3,"yes",Level Amount=SpellLevel(String), "Nah, you havetobe level %A to learn this one." +Topic=3,"yes",CountMoney "Hey! Whereisyourgold?" +Topic=3,"yes" -> "Hereyouare. It's now in your spellbook, jawoll.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe nexttime." +} diff --git a/data/npc/dustrunner.npc b/data/npc/dustrunner.npc new file mode 100644 index 0000000..0a8760b --- /dev/null +++ b/data/npc/dustrunner.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# dustrunner.npc: Datenbank fuer den Rennhund Dustrunner + +Name = "Dustrunner" +Outfit = (32,0-0-0-0) +Home = [32914,32076,6] +Radius = 14 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/data/npc/ebenizer.npc b/data/npc/ebenizer.npc new file mode 100644 index 0000000..072e9d1 --- /dev/null +++ b/data/npc/ebenizer.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ebenizer.npc: Datenbank für den Bankier Ebenizer + +Name = "Ebenizer" +Outfit = (128,59-95-87-76) +Home = [33175,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Yes? What may I do for you, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "It's not your turn %N. Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Have a nice day." + +"bye" -> "Have a nice day.", Idle +"farewell" -> * +"name" -> "My name? Ebenizer!" +"job" -> "I am running this Bank" +"time" -> "It is %T, precisely." +"king" -> "Hail to the king!" +"tibianus" -> * +"army" -> "Soldiers have not that much money that I would care about." +"ferumbras" -> "A true threat to wealth and trade." +"excalibug" -> "This weapon, if real, might be worth a lot." +"thais" -> "We are in constant contact with the city of Thais." +"tibia" -> "There are countless ways of profit in this world." +"carlin" -> "It's underdeveloped and economically insignificant." +"edron" -> "The riches of our isle are its mineral resources." +"news" -> "I only care about financial news." +"rumors" -> * + +@"gen-bank.ndb" +} diff --git a/data/npc/edala.npc b/data/npc/edala.npc new file mode 100644 index 0000000..5c786f2 --- /dev/null +++ b/data/npc/edala.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edala.npc: Datenbank für die heilerin edala (nahe Elfenstadt) + +Name = "Edala" +Outfit = (63,0-0-0-0) +Home = [32698,31718,2] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, please wait a moment %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I am a mystic of the suns. I provide protective blessings for those in need." +"name" -> "My name is Edala, pilgrim." + +"mystic" -> "We mystics are philosophers and healers." + + +"cenath" -> "I don't consider me a member of any caste, and I don't want to talk about this matter." +"kuridai" -> * +"deraisim" -> * +"abdaisim" -> * +"teshial" -> * + +"crunor" -> "Crunor is great in his beauty." +"priyla" -> "The daughter of the stars is my patron." + +"excalibug" -> "It is true that this weapon brings great power. But you should not look for power. It is wisdom you really need." +"news" -> "News? I don't care about news." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask my about the blessing you are interested in." +"spiritual" -> " You may receive the spiritual shielding in the whiteflowertemple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." + +"fire" -> "Do you wish to receive the blessing of the two suns? It will cost you 10.000 gold, pilgrim.",Price=10000, Topic=5 +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(103) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "Kneel down and receive the warmth of sunfire, pilgrim.", DeleteMoney, EffectOpp(13), SetQuestValue(103,3), Bless(3) +Topic=5,! -> "All right. As you wish." + + +} diff --git a/data/npc/eddy.npc b/data/npc/eddy.npc new file mode 100644 index 0000000..61b32ba --- /dev/null +++ b/data/npc/eddy.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eddy.npc: Möbelverkäufer Eddy auf Fibula + +Name = "Eddy" +Outfit = (128,60-64-0-95) +Home = [32168,32431,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Eddy. I sell furniture." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/edoch.npc b/data/npc/edoch.npc new file mode 100644 index 0000000..af10a09 --- /dev/null +++ b/data/npc/edoch.npc @@ -0,0 +1,48 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edoch.npc: Datenbank für den Bogenmacher Edoch + +Name = "Edoch" +Outfit = (129,95-0-40-116) +Home = [33232,32430,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings, traveller." + +"bye" -> "Daraman's blessings, traveller.", Idle +"job" -> "I am nothing but a humble fletcher. I am selling bows, crossbows, and ammunition. Do you need any of these?" +"fletcher" -> * +"name" -> "I am Edoch Ibn Ibrach." +"time" -> "You surely can buy a watch somewhere on this bazaar." +"tibia" -> "The world is vast and dangerous. Better prepare yourself with a bow before you travel out there." +"thais" -> "I was there once to learn about their ways. Needless to say I was horrified and returned to Darashia as soon as possible." +"do","you","sell" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/edowir.npc b/data/npc/edowir.npc new file mode 100644 index 0000000..141639f --- /dev/null +++ b/data/npc/edowir.npc @@ -0,0 +1,165 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edowir.npc: Datenbank fuer den Weisen Edowir + +Name = "Edowir" +Outfit = (130,0-58-96-95) +Home = [32343,32363,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, hello %N! How nice of you to visit an old man like me." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Be patient %N. Learn to listen, listen to learn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back whenever you're in need of wisdom." + +"bye" -> "Come back whenever you're in need of wisdom.", Idle +"farewell" -> * +"how","are","you" -> "I am fine, thank you." +"sell" -> "I sell nothing, but I share my wisdom now and then." +"job" -> "I gather wisdom and knowledge. I am also an astrologer." +"name" -> "I am Edowir, but don't worry about remembering my name. I will forget your name as well." +"edowir" -> "That's me, but don't worry about remembering my name. I will forget your name as well." +"time" -> "Time is a pillar and our lives wind around it like vine." +"help" -> "I would like to help you. What is your problem?" +"monster" -> "Man or monster, the difference is often just a matter of hides and scales." +"dungeon" -> "Dungeons are a place of danger, not of joy. Keep that in mind on your travels." +"sewer" -> "Sewers are sometimes the safer ways to get where you want to." +"god" -> "Learn about the gods to learn from the gods." +"king" -> "Kings are children adorned with crowns." +"bozo" -> "Who laughs last, thinks slowest." +"joke" -> * +"jester" -> * +"rumour" -> "Rumours are an unsafe path to follow." +"gossip" -> * +"fuck" -> "If that's all you can think about...", Idle +"weapon" -> "Those who live by the sword get shot by those who don't." +"magic" -> "I believe that true love is stronger than all magic, don't you agree?" +"old" -> "Growing old is mandatory, growing up is optional." +"age" -> * +"tibia" -> "If Tibia is a fallen god, does that makes us the maggots crawling on it?" +"castle" -> "A strong wall may protect from an assault, but what will protect you from the enemy within?" +"muriel" -> "Mages claim to be be wise, but how wise can it be to sacrifice your life to books and scrolls and not for the people?" +"sorcerer" -> * +"elane" -> "A paladin is more than just a knight armed with a bow and some spells, though most seem to be unaware of that fact." +"paladin" -> * +"marvik" -> "Druids seek enlightenment in nature, but they often just find what they brought with them." +"druid" -> * +"gregor" -> "Knights could be artists, but tend to become sellswords." +"knight" -> * +"necromant","nectar" -> "There is no such thing, believe me. The dead don't care for taste." +"goshnar" -> "The Necromant King. He is dead forever, and that is the nicest thing I can say about him. May he rot in his tomb." +"necromant" -> "How could they try to understand death, if they don't care to understand life?" +"nightmare","knight" -> "The Nightmare Knights were an ancient order dedicated to fight evil. They were guided by prophetic dreams. The order ceased to exist after their war against the Brotherhood of Bones." +"brotherhood","bone" -> "This brotherhood was an secret society of necromancers and followers of purest evil. They were vanquished long ago by their arch-enemies, the Nightmare Knights." +"pits","inferno" -> "An infernal place in which the nightmare knights created a base to fight the minions of evil. It was lost when the Ruthless Seven conquered it." +"ruthless","seven" -> "They are more than a myth, they are a horrible reality. It is possible that they still reside in the pits of inferno." +"sternum" -> "Behind the mountain lies a land of great danger." +"mintwallin" -> "The underground city of the minotaurs can be reached through a dangerous passage from the old temple." +"old","temple" -> "In the old days the underground temple was built for the glory of Banor after a victory over the orcish hordes. It is now an abandoned and dreary place overrun by rotworms." + +"carlin" -> "A city in the far north. It separated from the Thaian kingdom about 100 years ago. Now it is ruled by a dynasty of queens." +"thais" -> "Thais is the capital of an ancient human kingdom. Once its rule was more or less undisputed. In the years the strength of the thaian kingdom eroded by different events." +"ab'dendriel" -> "Although lovely, the city of the elves lacks the grace and the vibrance of the elven cities of old. The elves are still working on improvement of their settlement." +"Shadowthorn" -> "The elves of shadowthorn are hosile to intruders. Their Kuridai leaders practise some sinister cults and the other castes are more their minions then their equals." +"castes" -> "The elven society is divdided into certain cates, the cenath, the kuridai, the deaisim, the abdaisim and the legendary theshial." +"cenath" -> "The cenath favour magic above all other. They are the keeper of elven lore and wisdom. They are resposible for the astounding feats of druidic magic the elves are capable of." +"kuridai" -> "The Kuridai are the craftsmen and warriors of elvenkind. They are allways moving, allways sheming. They are the most agressive elves and distrust outsiders. An utsider might be each non-Kuridai to them." +"deraisim" -> "One could call the Deraisim the scouts and rangers of elvenkind. Although all elves are formidable in that area, the Deraisim excell them all." +"abdaisim" -> "The Abdaisim are what humans would call 'independent elves'. They take shelter wherever they might find it, are wanderers and explorers. They only keep loose contact with the elven society." +"teshial" -> "Its said that those elves were the masters of the dreams. Which many consider as a special brand of magic. However they seem to have vanished from the face of tibia ages ago and their fate is unknown." +"kazordoon" -> "The ancient fortrescity of the dwarf was carved into the mountain known as 'the big old one'. Its quite hidden and heavily guarded to withstand any assault." +"dwarf" -> "The small but strong dwarves are tireless workers and fierce warriors. They are familiar with several crafts and mastered most of them. In our days their smithing skills are rivaled only by those of the cyclopses." +"dwarv" -> * +"cyclops" -> "Cyclopses are seen as the smithes of blog, whom they call 'the ragehammer' or 'ragehammerer'. Indeed their skills create mostly crude and nasty looking weapons and armor which are incredicle effective nontheles." +"blog" -> "Blog is the god of rage and fierce battle. Hes also the patron of power, although a power to opress and bully others around. He is the son of Zathroth and one of the tibian suns." +"zathroth" -> "Zathroth is the dark twin of Uman. They are one and they are two seperate entities. We mortals can't realy grasp this concept. He is the patron of dark magic and even darker secrets, the lust for dominance +through cunning, and manipulation." +"zathroth" -> "Uman is the light twin of Zathroth. Their unity and seperatuion at once is a concept we cannot hope to grasp. He is the patron of light magic, the knowledge that beniefits all and brings progress to the society." +"venore" -> "Venore is a center of commerce and trade. Its ambitous trade-barons are nominaly subjects of the thaian kingdom." + +"paradox", "tower" -> "The paradox Tower was home of a mighty but mad wizard. Its said that only the cunning and mad can brave the tests of that tower to gain its treasures." +"ridler" -> "As far as I can tell this creature is not fond of cheaters and wont allow them to pass his tests." + +"magic","metal" -> "There are sevral kinds of magic metals in our world, the best known are called Mesh Kaha Rogh, Za'Kalortith, Uth'Byth, Uth'Morc, Uth'Amon, Uth'Maer, Uth'Doon, and Zatragil." + +"Mesh","Kaha","Rogh" -> "The so called singing steel causes a constant humming while its forged. It's said its a sign that it absorbs magic powers in the process and its probably easy to enchant it. However the secret where to mine or how to creat this ore is lost in time." +"Za'Kalortith" -> "This is the metal of 'evil'. The hell forged iron no ordinary flames can melt. Its rumored to be harvestet in hell from iron rocks in which damned souls were imprisoned." +"Uth'Byth" -> "This steel absorbs magic, its of inferior quality compared to ordinary steel but its absorbing qualities make it important though." +"Uth'Morc" -> "What makes this black steel special is its lightness and special property of lacking the common steel 'noise'. Its also called silent steel or thiefs steel for that reason." +"Uth'Amon" -> "The luminescent brightsteel is used for artwork mainly. In ancient times items of great magic power were created using the brightsteel. Those secrets were lost with the races which hold them as their secrets." +"Uth'Maer" -> "The dwarfs call it heartiron and claim its part of the heart of the big old one. Therefore is sacred and its use is limited and regulated." +"Uth'Doon" -> "The dwarfen high steel is relatively common but expensive and still hard to come by. The elite dwarfen weaponary and armors are made of Uth'Doon." +"Zatragil" -> "The so called dreamsilver is a legendary metal. Almost everything we know about it are rumours only." + + +"plains","havoc" -> "Somewhere in the Plains of Havoc, where the Necromant King was defeated lies the secret entrance to the pits of inferno." +"excalibug" -> "The ancient dwarfen kings forged it using magic metal , which they took from cyclopses who found it in the heart of a fallen star." +"venore" -> "The swamp city is a center of commerce and known for it riches and its merchant barons. It is part of the Thaian kingdom." +"rookgaard" -> "It was on rookgaard where the soul vortex appeared. The Thaian kingdom holds an outpost there to protect the vortex and to guide the newly arrived souls." +"soul","vortex" -> "The gods created the vortex to guide powerful souls to our world so they might join the battle for creation." +"magic","items" -> "Magic items are numerous. If you would like some details, please ask me about a specific item." +"bronze","amulet" -> "Some creatures are able to attack your magic power rather than your lifeforce. This amulet bestows some protection on you against those attacks." +"silver","amulet" -> "This amulet purifies your blood. It will reduce the damage caused by poison." +"platinum","amulet" -> "These powerful amulets are usually blessed by some god. They offer additional armour and protection." +"strange","talisman" -> "These amulets protect you from harm taken by energy attacks and magic fields." +"amulet","life" -> "These amulets were created and enchanted by powerful magicians and priests. They protect both body and soul from the losses caused by the trauma of death." +"stone","skin","amulet" -> "Though they possess only a few charges, stone skin amulets are sought after because they offer complete protection from physical damage." +"dragon","necklace" -> "The core piece of these amulets is a little dragon scale. It protects against fire damage to some extent." +"garlic","necklace" -> "This charm, feared and despised by the undead, protects your lifeforce from lifedraining powers." +"elven","amulet" -> "These ancient elven artifacts are highly enchanted and grant some protection against any each form of damage." +"shielding","amulet" -> "These amulets are a more powerful version of the elven amulets. They were created by a race long gone from this plane and offer significant protection against every kind of damage." + +"mightring" -> "This ring will give you limited protection against any kind of damage." +"swordring" -> "This ring will increase your skill when wielding swords." +"axering" -> "This ring will increase your skill when wielding any kind of axe." +"clubring" -> "This ring will increase your skill when wielding a club weapon." +"powerring" -> "This kind of ring will increase your skill when fighting with bare hands." +"timering" -> "These rings warp the fabric of time, greatly enhancing your running speed." +"lifering" -> "These rings improve your regenerative powers, accelerating the recovery of both your mana and your lifeforce." +"ring","healing" -> "This ring increases the rate with which you heal your physical wounds." +"stealthring" -> "These rings were created by an ancient, long forgotten race. It is said they valued secrecy above all. They used these magic rings to make themselves invisible." +"dwarvenring" -> "Actually rings of this kind are not created by dwarves. However, if you wear one you can drink as though you were a dwarf. They give you partial immunity against drunkenness." +"energyring" -> "These rings were created by the sorcerers' guilds of old. They temporarily provide their wielders with a shield of magic." + +"ghostlands" -> "The ancient structures that were found deep beneath the ghostlands were built by an unkown race for an unknown purpose. Its quite certain that, whatever they were once used for, they now cause madness and ghost sightings in the sourounding area." +"banshee" -> "The banshees were creatures that grief and despair turned into vengefull spirits after their deaths. Their wail is deadly and they draw new strength from the pain and fear of others." +"banshee","queen" -> "The legendary banshee queen is incerdibly old and likely next to invincible. Shes rumored to have her lair in the deepest caverns of the ghotlands. She seems to be more likely to talk then her sisters but is for sure even more evil then them." +"queen","banshee" -> * + +"hugo" -> "I think you are referring to the beast Hugo that is said to still haunt the Plains of Havoc. The legends which tell of this creature are ancient and almost forgotten." +"legend" -> "As far as we know, once a terrible beast roamed the lands we now call the Plains of Havoc. It was so fierce that no one dared to even dream about killing it. Finally it was tricked by the knight Endulos." +"endulos" -> "Endulos was not a great warrior, but a man of wit and genius. After many of his brethren of the Nightmare Knights had fallen prey to the beast, he came up with a cunning plan to end that threat." +"plan" -> "He lured Hugo into a trap. Bound by roots and stones charged with powerful magic he could not move anymore. Now, the beast lies trapped in a hidden cave for eternity." +"cave" -> "The legends tell us that the Nightmare Knights trapped it beneath one of their fortresses, or rather that they built a fortress on top of his eternal prison." +"ghostland" -> "The Ghostlands are haunted by their past. In bygone days some ancient race lived there. Deep beneath the earth some of their structures are still intact and defile the surrounding lands." +"defile" -> "Whatever the original meaning of that underground complex was, it is now like an open wound in the nearby lands, spreading madness and attracting all kinds of ghosts and apparitions." +"edron" -> "Edron is the latest colony of the Thaian kingdom. However, structures of an earlier colonisation have been found. We cannot tell if those inhabitants were human or of any other known race." +"daraman" -> "Daraman was a sage with ambition, that is for sure. His philosophy centered around the idea that by controlling yourself you could improve yourself. The closer you are coming to perfection, the closer you are to ascension to divinity." +"darama" -> "The desert lands of Darama are harsh and unforgiving. Therefore Daraman led his people there to found a new community based upon his teachings." +"darashia" -> "The town of Darashia is built around one of the few sweet water supplies of Darama. It is famous for its sand wasp honey and its sandworm stew." +"drefia" -> "The dreaded town of Drefia was once a haven for heretics, necromancers and demon worshipper. Its was destroyed in the war of the Djinn." +"war", "djinn" -> "Although some priests claim that war was fought on behalf of the gods, it seems more likely that this was kind of a civil war between the Djinn of good and the Djinn of evil." +"djinn" -> "Legend has it that the Djinn were created by Zathroth by using the stolen chalice of life. They roamed the world for Aeons causing strife and despair until Gabel, one of their lords met a very special human." +"gabel" -> "Gabel was the most powerful among the Djinn lords. He was cruel and merciless, until one day his minions brought a certain human to him whom they had captured and tortured." +"certain","human" -> "This human showed no fear and did not yield under torture. They could bend him, but they never managed to break him. Impressed by this mortal, Gabel talked to him and learned about his philosophy." +"philosophy" -> "The human whom the Djinn had caught was none other but Daraman. The mighty Gabel was intrigued by his philosophy an changed his ways according to Daramans teachings." +"teachings" -> "The teachings of asceticism, inner peace and ascension appealed to the Djinn, although in the beginning this was probably only because of his vanity and his greed for divinity. However, Malor and his followers opposed him." +"malor" -> "Malor was second in power only to Gabel and his followers among the Djinn were many. It was not easy for the evil Djinn to change their ways, and many preferred to follow Malor instead of Gabel. In the end a civil war erupted." +"civil","war" -> "In the war of the Djinn citys were levelled, lands were cursed and islands sunk. Finally, Malor was caught and imprisoned in an enchanted bottle. His followers fled this plane, while the good djinn laid to rest to recover their strength." +"chalice","life" -> "This chalice was a tool of the gods which they created to make their task to create life easier. Zathroth who lacked the knowledge of creation stole that chalice and used it to spawn his evil minions." +"minion","evil" -> "The Djinn were the result of his first attempts. They were powerful and quite evil, but not as evil as Zathroth wished and quite independent in their thinking. Finally he discarded them and decided his second try would become his masterpiece." +"masterpiece" -> "Zathroth channeled all the hatred and foulness he could muster. He added the burning rage of his son Blog and mixed it with fire. The energy that was released destroyed the chalice, but Zathroth had succeeded in creating the first demon." +"demon" -> "Demons are the servants of evil. More or less devoted servers of Zathroth they cause strife and havoc wherever they appear. Their masters are known as Demonlords, Demon Overlords and Archdemons." +"demonlords" -> "Demonlords are the generals of their kind. They are more cunning than ordinary demons, and they can channel their hatred more effectively than their lesser brethren, making them even more formidable opponents." +"demon","overlords" -> "The overlords of the demonkind are more powerful than even demonlords are. They are nearly indestructible. Armoured with layers of impenetrable hide and endowed with awesome magical power, demon overlords are true incarnation of death." +"archdemons" -> "The archdemons are few, and they are extremely rare. And a good thing, too, for they are the rulers of the demonrace. They are vain and powerhungry creatures who tend to form only small cabals and fight each other instead of allying up against creation." +"cabals" -> "There are at least five demonic cabals of archdemons. The ruthless seven are the most prominent and powerful." +} + + + + + diff --git a/data/npc/edvard.npc b/data/npc/edvard.npc new file mode 100644 index 0000000..f6d150f --- /dev/null +++ b/data/npc/edvard.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# edvard.npc: Möbelverkäufer Edvard in Edron + +Name = "Edvard" +Outfit = (128,59-115-96-95) +Home = [33229,31831,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Edron Furniture Store, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Edvard. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/elane.npc b/data/npc/elane.npc new file mode 100644 index 0000000..90834f1 --- /dev/null +++ b/data/npc/elane.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elane.npc: Datenbank für die Paladinin Elane + +Name = "Elane" +Outfit = (137,113-63-120-119) +Home = [32343,32239,7] +Radius = 4 + +Behaviour = { +ADDRESS,Paladin,"hello$",! -> "Hi, %N! What can I do for you?" +ADDRESS,Paladin,"hi$",! -> * +ADDRESS,"hello$",! -> "Welcome to the paladins, %N! Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am the leader of the Paladins. I help our members." +"name" -> "My name is Elane. I am the famous leader of the Paladins." +"time" -> "Oops. I have forgotten my watch." +"king" -> "King Tibianus is a wise ruler." +"tibianus" -> * +"quentin" -> "A humble monk and a wise man." +"lynda" -> "Hm, a litte too nice for my taste." +"harkath" -> "A fine warrior and a skilled general." +"army" -> "Some paladins serve in the kings army." +"ferumbras" -> "Someday I will slay that bastard!" +"general" -> "Harkath Bloodblade is the royal general." +"sam" -> "Strong man. But a little shy." +"gorn" -> "He sells a lot of useful equipment." +"frodo" -> "The alcohol he sells shrouds the mind and the eye." +"galuna" -> "One of the most important members of our guild. She makes all the bows and arrows we need." +"bozo" -> "How spineless do you have to be to become a jester?" +"baxter" -> "He has some potential." +"oswald" -> "If there wouldn't be higher powers to protect him..." +"sherry" -> "The McRonalds are simple farmers." +"donald" -> * +"mcronald" -> * +"elane" -> "Yes?" +"muriel" -> "Just another arrogant sorcerer." +"gregor" -> "He and his guildfellows lack the grace of a true warrior." +"marvik" -> "A skilled healer, that's for sure." +"lugri" -> "A follower of evil that will get what he deserves one day." +"excalibug" -> "A weapon of myth. I don't believe that this weapon exists." +"news" -> "I am a paladin, not a storyteller." + +"member" -> "Every paladin profits from his vocation. It has many advantages to be a paladin." +"profit" -> "We will help you to improve your skills. Besides I offer spells for paladins." +"advantage" -> "We will help you to improve your skills. Besides I offer spells for paladins." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Paladins, knights, sorcerers, and druids." +"paladin" -> "Paladins are great warriors and magicians. Besides that we are excellent missile fighters. Many people in Tibia want to join us." +"skill" -> "Paladins are great warriors and magicians. Besides that we are excellent missile fighters. Many people in Tibia want to join us." +"warrior" -> "Of course, we aren't as strong as knights, but no druid or sorcerer will ever defeat a paladin with a sword." +"magician" -> "There are many magic spells and runes paladins can use." +"missile" -> "Paladins are the best missile fighters in Tibia!" +"spellbook" -> "In a spellbook your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Xodet in his magic shop." +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Bye.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/data/npc/elathriel.npc b/data/npc/elathriel.npc new file mode 100644 index 0000000..7607c43 --- /dev/null +++ b/data/npc/elathriel.npc @@ -0,0 +1,94 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# erathriel.npc: Datenbank für den Kuridai-Anführer Elathriel (Elfenstadt) + +Name = "Elathriel" +Outfit = (64,0-0-0-0) +Home = [32684,31671,9] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up! Can't you see that I am talking?!" +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi, stranger.", Idle +"farewell" -> * +"asha","thrazi" -> * +"name" -> "Not that I like to talk to you, but I am Elathriel Shadowslayer." +"job" -> "I am the leader of the Kuridai and the Az'irel of Ab'dendriel. Humans would call it sheriff, executioner, or avenger." +"sheriff" -> "Sometimes people get imprisoned for some time. True criminals will be cast out and for comitting the worst crimes offenders are thrown into the hellgate." +"executioner" -> * +"avenger" -> * + +"hellgate" -> "It was here among other structures, like the depot tower, before our people came here. It's secured by a sealed door." +"door" -> "For safety we keep the door to the hellgate locked all times. I have the keys to open it when needed." +"sealed" -> * +"key" -> Type=2970, Data=3012, Amount=1, Price=5000, "If you are that curious, do you want to buy a key for %P gold? Don't blame me if you get sucked in.", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Believe me, it's better for you that way." + +"time" -> "I couldn't care less." + +"carlin" -> "We watch this city and the actions of its inhabitants closely." +"thais" -> "The thain kingdom and we share some enemys, so its only logical to cooperate in a few areas." +"venore" -> "The merchants of venore provide us with some usefull goods. Still I an convinced that they get more out of our bargain then we do." +"roderick" -> "He is tolerated here as the spokesman of the thaian king." +"olrik" -> "This human is too unimportant to be even mentioned." + +"king" -> "It's hard for some of my people to grasp the true concept of a strong leader." +"tibianus" -> "A human weakling, not much more." +"eloise" -> * +"elves" -> "My people are divided in castes in these times, until they comprehend that only the way of the Kuridai can save us all." +"dwarfs" -> "We might use the shelter earth and hills provide us, but their obsession for metal is a waste of time." +"humans" -> "They are useful ... and better stay useful." +"troll" -> "Like all inferior races they can be at least used for something good. The other castes are just jealous about our use of them." +"army" -> "It's one of the more useful concepts we can learn from the other races." +"cenath" -> "Arrogant bastards, but they wield quite powerful magics." +"kuridai" -> "We are the heart of the elven society. We forge, we build, and we don't allow our people to be pushed around." +"deraisim" -> "Confused cowards. With all their skill they still tend to hide and run. What a waste." +"abdaisim" -> "Even more undecided then the deraisim." +"teshial" -> "Dreamers are of no practical use. I don't mourn their demise." +"ferumbras" -> "Even if he'd walk through the town above the other castes won't see the necessity to follow OUR way." +"crunor" -> "I have no use for the treething. I worship Mortiur, the ravager, of course." +"mortiur" -> "The celestial paladin of revenge. He was one of the greatest elven wariors of all times." +"excalibug" -> "I still doubt it exists." +"news" -> "News are confidential and not your business." + +"magic" -> "I mastered some spells of battle." +"druid" -> "Druids' magic is too peaceful for my taste." +"sorcerer" -> "I have seen human sorcerers doing some impressive things ... before they died." +"spellbook" -> "I don't sell such stuff." +"spell" -> "I teach the spells 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball', 'Great Fireball', 'Fire Bomb', and 'Explosion'." + +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 + +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 + +"light","missile" -> "I'm sorry, but this spell is only for druids and paladins." +"heavy","missile" -> * +"fireball" -> * +"great","fireball" -> "I'm sorry, but this spell is only for druids." +"fire","bomb" -> * +"explosion" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/data/npc/elfguard.npc b/data/npc/elfguard.npc new file mode 100644 index 0000000..a5626a6 --- /dev/null +++ b/data/npc/elfguard.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elfguard.npc: Datenbank für die Elfenwache in Ab'Dendriel + +Name = "Elf Guard" +Outfit = (63,0-0-0-0) +Home = [32642,31709,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "My name is unimportant, only my duty does matter." +"job" -> "I am a guardian of this town. I have no time to chat!" +"time" -> "It's %T." +"town" -> "This is the elven town of Ab'Dendriel." +"city" -> * +"ab'dendriel" -> * +"thais" -> "The city of the humans lies somewhere far to the south beyond the mountains of the dwarfs." +"carlin" -> "This city of humankind is located to the west of our area." +"kazordoon" -> "The dwarfish settlement is hidden somewhere in the mountains in the south." +"elf" -> "The elves of this city are the casts of the Cenath, the Kuridai, and the Deraisim." +"elves" -> * +"cenath" -> "The Cenath are magic users. Look for them on the upper levels of the town." +"kuridai" -> "The Kuridai are the smiths and craftsmen. Look for them in the underground parts of the city." +"deraisim" -> "The Deraisim are scouts and hunters. You may find them on the groundlevel of the city." +"abdaisim" -> "The Abdaisim are wanderers. Since they live as nomads and travel the world you won't find them here." +"teshial" -> "There are no Teshial." +"ferumbras" -> "He is not allowed to enter this city." +"army" -> "Such a thing is a human concept. We have no need for that, though some Kuridai might think otherwise." +"spell" -> "Ask around in Ab'Dendriel. Many elves can teach you something about magic. The Cenath love magic most of all." +"magic" -> * +"armor" -> "If you are looking for that kind of equipment you should ask a Kuridai." +"weapon" -> * +"food" -> "Ask some Deraisim where you can get food." + +"tha'shi","ab'dendriel" -> "In the crude human language you would translate it with 'My life for Ab'Dendriel' or even 'I am one with Ab'Dendriel'." +"bahaha","aka" -> "This means 'Take your punishment, defiler'." +} diff --git a/data/npc/elvith.npc b/data/npc/elvith.npc new file mode 100644 index 0000000..4c27c6b --- /dev/null +++ b/data/npc/elvith.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# elvith.npc: Datenbank für den Musiker Elvith + +Name = "Elvith" +Outfit = (144,76-3-0-76) +Home = [32669,31607,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"Ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"name" -> "I am Elvith Rollingstone." +"job" -> "I sell musical instruments of many kinds." +"time" -> "Time has its own song. Close your eyes and listen to the symphony of the seasons." + +"carlin" -> "Carlin is a city that thrives for a harmony it can never achive." +"thais" -> "I heared about Thais and id did not sound like a place I'd want to visit." +"venore" -> "By all what I heared this city is not only built into a swamp but its a swamp of intrigue and corruption itself." +"roderick" -> "This man trys too hard not to offend someone." +"olrik" -> "He appreciates my music and allthough he is loud and clumsy as all humans it seems not everything is lost." + +"music" -> "Music is an attempt to condensate emotions in harmonies and save them for the times to come." +"harmonies" -> "Everything is a song. Life, death, history ... everything. To listen to the song of something is the first step to understand it." +"melodies" -> * +"harmony" -> * +"melody" -> * +"song" -> * +"sing" -> "Sorry, but there is a melody in my heart that wants to be born. I would loose it before by singing right now." +"elf" -> "We are the most graceful of all races. We feel the music of the universe in our hearts and souls." +"elves" -> * +"dwarf" -> "They could at least use their picks and hammers with more rythm." +"human" -> "They are too loud and don't even understand the concept of a melody." +"troll" -> "I went down to the mines and tried to lighten up their spirit, the foolish creatures did not listen to my songs, though." +"cenath" -> "The Cenath think they know the 'art' but the only true art is the music." +"kuridai" -> "They could dig some halls for a big musical event, but they won't listen to me about that matter." +"deraisim" -> "The other deraisim are too much concerned with mastering the nature so they don't listen to its music anymore." +"abdaisim" -> "The wanderers have no patience. You need patience and passion to create and to enjoy music." +"teshial" -> "I bet they were great musicians." +"ferumbras" -> "Only humans made songs about him and his evil deeds." +"crunor" -> "That is some god the humans worship. Our pople are not interested in this gods anymore." +"excalibug" -> "There are too many songs about that weapon to retell them all. Most of them are human and therefore quite crude anyways." +"spell" -> "Sorry, I don't feel like teaching magic today." +"magic" -> * + +"elven", "poetry",QuestValue(311)=1,QuestValue(312)=0 -> "The last issue I had was bought by Randor Swiftfinger. He was banished through the hellgate and probably took the book with him ...", "I would not recommend to seek him or the book there but of course its possible." +"song", "forest",QuestValue(311)=1 -> * + +"elven", "poetry",QuestValue(311)=1,QuestValue(312)=1 -> Type=4844, Amount=1, Price=500,"By luck I aquired another copy of the book you are looking for. Do you want to buy a copy of 'songs of the forest' for 500 gold?.",topic=2 +"song", "forest",QuestValue(311)=1 -> * +Topic=2,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Sorry, you do not have enough gold." +Topic=2 -> "Maybe you will buy it another time." + + + +"elven", "poetry" -> "Sorry, I have no issue of this book left." +"song", "forest" -> * + +"hellsgate" -> "For the worst of crimes the criminals are cast into hellgate. Its said noone can return from there. Since it is not forbidden to enter hellgate you might convince Elathriel to grant you entrance." +"elathriel" -> "He is a kuridai and the local Az'irel. Something like the head of the human townsguards." + +"offer" -> "I sell lyres, lutes, drums, and simple fanfares." +"goods" -> * +"buy" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"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 + +%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 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drum for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfare for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/data/npc/eremo.npc b/data/npc/eremo.npc new file mode 100644 index 0000000..f0d39e1 --- /dev/null +++ b/data/npc/eremo.npc @@ -0,0 +1,127 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eremo.npc: Datenbank für den Weisen Eremo + +Name = "Eremo" +Outfit = (130,0-109-128-95) +Home = [33322,31883,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little garden, adventurer %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,premium,promoted,"hello$",! -> "Welcome to my little garden, humble %N!" +ADDRESS,premium,promoted,"hi$",! -> * +ADDRESS,premium,promoted,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Shouldn't I teleport you back to Pemaret?" + +"bye" -> "Shouldn't I teleport you back to Pemaret?", Idle +"farewell" -> * +"name" -> "I am Eremo, an old man who has seen many things." +"job" -> "I teach some spells, provide one of the five blessings, and sell some amulets." +"offer" -> * +"magic" -> * +"island" -> "I have retired from my adventures to this place." +"isle" -> * +"garden" -> * +"adventure" -> "I explored dungeons, I walked through deserts, I sailed on the seas and climbed up on many a mountain." +"thing" -> * +"Tibia" -> "A great world full of magic and wonder." + +"amulet",PvPEnforced -> "I've collected quite a few protection amulets. Also, I'm interested in buying broken amulets." +"amulet" -> "I've collected quite a few protection amulets, and some amulets of loss as well. Also, I'm interested in buying broken amulets." + + +"amulet","of","loss" -> Type=3057, Amount=1, Price=50000, "Do you want to buy an amulet of loss for %P gold?", Topic=3 +"amulet","of","loss",PvPEnforced,! -> "What a strange name for an amulet. Never heard about that one." + + +"protection","amulet" -> Type=3084, Amount=1, Price=700, "Do you want to buy a protection amulet for %P gold?", Topic=3 +"broken","amulet" -> Type=3080, Amount=1, Price=50000, "Do you want to sell a broken amulet for %P gold?", Topic=4 +"amulet","of","life" -> * + +premium,promoted,"spell" -> "I can teach 'Enchant Staff' to sorcerers, 'Challenge' to knights, 'Wild Growth' to druids, and 'Power Bolt' to paladins." +"spell" -> "I am sorry, but you are not promoted yet." + +sorcerer,premium,promoted,"enchant","staff" -> String="Enchant Staff", Price=2000, "Do you want to learn the spell 'Enchant Staff' for %P gold?", Topic=1 +"enchant","staff" -> "I am sorry but this spell is only for master sorcerers." + +knight,premium,promoted,"challenge" -> String="Challenge", Price=2000, "Do you want to learn the spell 'Challenge' for %P gold?", Topic=1 +"challenge" -> "I am sorry but this spell is only for elite knights." + +druid,premium,promoted,"wild","growth" -> String="Wild Growth", Price=2000, "Do you want to learn the spell 'Wild Growth' for %P gold?", Topic=1 +"wild","growth" -> "I am sorry but this spell is only for elder druids." + +paladin,premium,promoted,"power","bolt" -> String="Power Bolt", Price=2000, "Do you want to learn the spell 'Power Bolt' for %P gold?", Topic=1 +"power","bolt" -> "I am sorry but this spell is only for royal paladins." + +"teleport" -> "Should I teleport you back to Pemaret?",Topic=2 +"pemaret" -> * +"back" -> * +"cormaya" -> * +"edron" -> * + +Topic=1,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "You must be have level %A or better to learn this spell." +Topic=1,"yes",CountMoney "Oh. You do not have enough money." +Topic=1,"yes" -> "Here you are. Look in your spellbook for the pronounciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Fine. Do as you please." + + +Topic=2,"yes",PZBlock,! -> "Your soul is imbalanced by death and murder. Try again after you regained your balance!" + +Topic=2,"yes" -> "Here you go!", Idle, EffectOpp(11), Teleport(33288,31956,6), EffectOpp(11) +Topic=2 -> "Maybe later." + +Topic=3,"yes",CountMoney>=Price -> "Thank you. Use it wisely.", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Sorry, you do not have enough gold." +Topic=3 -> "Maybe another time." + +Topic=4,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=4,"yes" -> "Sorry, you do not own one." +Topic=4 -> "Maybe another time." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of Tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just tell me in which of the five blessings you are interested." + +"spiritual" -> " You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of Tibia." + +"fire" -> "You should ask for the blessing of the two suns in the suntower near Ab'Dendriel." +"suns" -> * +"wisdom" -> "I can provide you with the wisdom of solitude. But you will have to sacrifice 10.000 gold to receive it. Are you still interested?",Price=10000,Topic=5 +"solitude" -> * + +Topic=5,"yes", QuestValue(101) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the wisdom of solitude, pilgrim", DeleteMoney, EffectOpp(13), SetQuestValue(101,1), Bless(4) +Topic=5,! -> "Ok. As you wish." + + +} + diff --git a/data/npc/eroth.npc b/data/npc/eroth.npc new file mode 100644 index 0000000..58ed633 --- /dev/null +++ b/data/npc/eroth.npc @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eroth.npc: Datenbank für den Cenath-Anführer Eroth (Elfenstadt) + +Name = "Eroth" +Outfit = (63,0-0-0-0) +Home = [32661,31685,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "I greet thee, outsider." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence!" +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi. Go, where you have to go.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the leader of the Cenath caste." +"name" -> "My name is Eroth Ramathi." +"time" -> "That is a inferior concept invented by the minor races." + +"carlin" -> "Their druids seek my counsel quite often. I provide them with as many insights their little minds can keep up with and I feel appropriate." +"thais" -> "A city of filth and dirt. Any elf should visit this city at least once to see what a society without good guidance can become." +"venore" -> "The merchants of venore have prooven usefull and are therefore tolerated." +"roderick" -> "A stupid human who won't comprehend our complex society." +"olrik" -> "A human who dreams to become an elf. It would be funny if it were not that pathetic." + +"king" -> "Our people have no use for kings or queens." +"tibianus" -> * +"elves" -> "Our people are the children of light and darkness, the heirs of dusk and dawn." +"dwarfs" -> "The diggers are not welcome in our realm." +"humans" -> "We tolerate them and allow them to be used by us." +"troll" -> "The Kuridai have the distasteful habit to keep some trolls for inferior work." +"army" -> "Stop this Kuridai nonsense." +"cenath" -> "We are the shepherds of our people. The other castes need our guidance." +"kuridai" -> "The Kuridai are aggressive and victims of their instincts. Without our help they would surely die in a foolish war." +"deraisim" -> "They lack the understanding of unity. We are keeping them together and prevent them from being slaughtered one by one." +"abdaisim" -> "They are fools and almost deserve the extinction that awaits them. Though we will take it upon us to rescue even them by leading them home." +"teshial" -> "They are gone. They alone were almost equal to us Cenath among elvenkind." +"dreamer" -> "The Teshial were masters of the so called dream magic." +"dream","master"-> "The dream masters, though overestimated, wielded some impressive power without much practical use." +"dreammaster" -> * +"ferumbras" -> "A human born evil. Another evidence of the destructive potential of that race." +"crunor" -> "Gods are for the weak. We will master the world on our own. We need no gods." +"excalibug" -> "Just another human myth." +"news" -> "I heared the new human settlement in the west became independent from the human empire." + +"magic" -> "Magic comes almost naturally to the Cenath. We keep the secrets of ages." +"druid" -> "Druids master spells of defence, healing, and nature." +"sorcerer" -> "Sorcerers are not attuned to nature and therefore can't master it." +"vocation" -> "You are narrow minded to think in such boundaries." +"spellbook" -> "Cenath rarely use spellbooks. The minor castes rely on them though." +"spell" -> "I can teach the spells 'Magic Shield', 'Invisible', 'Destroy Field', 'Creature Illusion', 'Chameleon', 'Convince Creature', and 'Summon Creature'." + +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +"magic","shield" -> "I'm sorry, but this spell is only for druids and paladins." +"destroy","field" -> * +"chameleon" -> "I'm sorry, but this spell is only for druids." +"creature","illusion" -> * +"convince","creature" -> * +"summon","creature" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/data/npc/etzel.npc b/data/npc/etzel.npc new file mode 100644 index 0000000..a793db7 --- /dev/null +++ b/data/npc/etzel.npc @@ -0,0 +1,152 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# etzel.npc: Datenbank für den Magier Etzel + +Name = "Etzel" +Outfit = (66,0-0-0-0) +Home = [32626,31917,5] +Radius = 3 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Hiho and welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> * +ADDRESS,Sorcerer,"hiho$",! -> * +ADDRESS,"hello$",! -> "Hiho, %N. " +ADDRESS,"hi$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Patient, young %N! Wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care out there, young one. " + +"bye" -> "Take care out there, young one. ", Idle +"farewell" -> * +"job" -> "I am der dwarfish mastermage. I am keeper of the secrets of magic." +"name" -> "My name is Etzel Fireworker, son of fire, of the Molten Rocks." +"time" -> "It's precisely %T now." +"wisdom" -> "Wisdom is not aquired cheeply." +"sorcerer" -> "Sorcery is not for the lazy or the impatient." +"power" -> "Great power brings great responsibility, young one." +"arcane" -> * +"responsibility" -> * +"vocation" -> "Being sorcerer is belonging to a vocation of great arcane power and responsibility." + + +"rune" -> "Sorry, I don't sell these anymore. I'm old and have to focus on more important things. Please ask my brother Sigurd next door. " +"life","fluid" -> * +"mana","fluid" -> * +"blank","rune" -> * +"spellbook" -> * + + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Take care out there, young one. ", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> " Come on, young one, you already know this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need some more money." +Topic=3,"yes" -> "IT BE! Now look into your spellbook for the words of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +Topic=4,"yes",CountMoney>=Price -> "Here, young one.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Not enough money." +Topic=4 -> "As you wish." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=7 +"vial" -> * +"flask" -> * + +Topic=6,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=6,"yes" -> "Come back, when you have enough money." +Topic=6 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=7,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=7,"yes" -> "You don't have any empty vials." +Topic=7 -> "Hmm, but please keep Tibia litter free." + + +} diff --git a/data/npc/eva.npc b/data/npc/eva.npc new file mode 100644 index 0000000..81ed0a7 --- /dev/null +++ b/data/npc/eva.npc @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# eva.npc: Datenbank für die Bankangestellte Eva (Carlin) + +Name = "Eva" +Outfit = (136,96-60-95-0) +Home = [32325,31780,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Take a seat, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I work in this bank. I can change money for you." +"name" -> "I am Eva." +"time" -> "It is exactly %T." + +@"gen-bank.ndb" +} diff --git a/data/npc/explorer.ndb b/data/npc/explorer.ndb new file mode 100644 index 0000000..eb81b5a --- /dev/null +++ b/data/npc/explorer.ndb @@ -0,0 +1,324 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-expl.ndb: Datenbank für die Explorers Society + +"job" -> "I am the local representative of the explorer society." + +"explorer", "society" -> "Our noble society is dedicated to explore the unknown. No location is too remote for our members to travel there ...", "No beast is too wild to be hunted. No treasure buried too deep to be unearthed ...", "Only the most dedicated and fearless adventurers may join our ranks." +"base" -> "Currently we maintain public bases in Port Hope and Northport." +"join",QuestValue(300)>0 -> "But you are already a member, %N" +"join",QuestValue(300)<1 -> "Do you want to join the explorer society?",topic=1 +"no", topic=1 -> "I see. Not everyone has the guts it takes to become a professional explorer." +"yes", topic=1 -> "Fine, though it takes more than a mere lip service to join our ranks. To prove your dedication to the cause you will have to acquire an item for us ...", "The mission should be simple to fulfil. For our excavations we have ordered a sturdy pickaxe in Kazordoon. You would have to seek out this trader Uzgod and get the pickaxe for us ...", "Simple enough? Are you interested in this task?", topic=2 + +"no", topic=2 -> "I see. Not everyone has the guts it takes to become a professional explorer." +"yes", topic=2 -> "We will see if you can handle this simple task. Get the pickaxe from Uzgod in Kazordoon and bring it to one of our bases. Report there about the pickaxe.", SetQuestValue(325,1) + +"pickaxe",QuestValue(300)<1 -> Type=4845,Amount=1,"Did you get the requested pickaxe from Uzgod in Kazordoon?",topic=9 +################# +"no", topic=9 -> "Get that special pickaxe from Uzgod in Kazordoon." +"yes", topic=9,Count(Type) "Get that special pickaxe from Uzgod in Kazordoon." +"yes", topic=9,Count(Type)>=Amount -> "Excellent, you brought just the tool we need! Of course it was only a simple task. However ...", "I officially welcome you to the explorer society. From now on you can ask for missions to improve your rank.",Delete(Type), SetQuestValue(300,1) + +"rank",QuestValue(300)>0,QuestValue(300)<4 -> "You are a novice of the explorer society." +"rank",QuestValue(300)>3,QuestValue(300)<7 -> "You are a journeyman of the explorer society." +"rank",QuestValue(300)>6,QuestValue(300)<9 -> "You are a relic hunter of the explorer society." +"rank",QuestValue(300)>8 -> "You are an explorer of the explorer society." +################## Allgemeine Missionen +"ratha",QuestValue(302)=0 -> "Ratha was a great explorer and even greater ladies' man. Sadly he never returned from a visit to the amazons. Probably he is dead ...", "The society offers a substantial reward for the retrieval of Ratha or his remains. Do you have any news about Ratha?",Type=3207,Amount=1,Price=250, topic=3 +"ratha",QuestValue(302)=1 -> "Ratha was a great explorer and even greater ladies' man. Thank you for returning his remains." + +"no",topic=3 -> "If you ever stumble across some information about Ratha, let us know." +"yes",topic=3,QuestValue(302)=0,Count(Type) "What are you talking about? You have nothing to clarify Ratha's whereabouts." +"yes",topic=3,QuestValue(302)=0,Count(Type)>=Amount -> "Poor Ratha. Thank you for returning this skull to the society. We will see to a honourable burial of Ratha.", Delete(Type), CreateMoney, SetQuestValue(302,1) + + +"brooch" -> "Our members travel to far away places and cross dangerous areas, many fall prey to enemies or the land ...", "Sometimes the personal explorer brooches can be recovered. That way we learn about the fate of our members ...", "We offer a reward for each brooch returned to us. Have you found an explorer brooch?",Type=3005,Amount=1,Price=50, topic=53 + +"no", topic=53 -> "Sometimes no news is good news." +"yes", topic=53,Count(Type)>=Amount -> "It's always a sad day when we learn about the death of a member. But at least we learnt about his fate. Thank you, here is your reward.", Delete(Type), CreateMoney +"yes", topic=53,Count(Type) "You don't have any lost brooch with you. This is not a topic to make fun of." + +"smith", "hammer" -> "The explorer society is looking for a genuine giant smith hammer for our collection. It is rumoured the cyclopses of the Plains of Havoc might be using one. Did you by chance obtain such a hammer?",Type=3208,Amount=1,Price=250, topic=4 +"smith", "hammer" ,QuestValue(303)=1 -> "The explorer society was looking for a genuine giant smith hammer until you brought us one. Thank you again." + +"yes", topic=4,Count(Type)>=Amount -> "Marvellous! You brought a giant smith hammer for the explorer society!", Delete(Type), CreateMoney, SetQuestValue(303,1) +"yes", topic=4,Count(Type) "This is no giant smith hammer." +"no", topic=4 -> "Just as you like. But think about the reward!" + +####### + +"hydra","egg" -> "The examination of hydra eggs is a valuable source of information. We buy hydra eggs for 500 gold. Are you interested in selling one?",Type=4839,Amount=1,Price=500, topic=40 + +"sell",%1,1<%1,"hydra","egg" -> Type=4839, Amount=%1, Price=500*%1, "The examination of hydra eggs is a valuable source of information. We buy hydra eggs for 500 gold each. Do you want to sell %A hydra eggs for %P gold?", Topic=40 + +"no",topic=40 -> "If you ever aquire a hydra egg, bring it here." +"yes",topic=40,Count(Type) "What are you talking about? You don't own that many hydra eggs." +"yes",topic=40,Count(Type)>=Amount -> "Thank you in the name of science.", Delete(Type), CreateMoney +#### +"scroll","lizard" -> "The examination of scrolls with lizard writings is a valuable source of information. We buy such scrolls for 500 gold. Are you interested to sell one?",Type=4831,Amount=1,Price=500, topic=41 +"parchment" -> * + +"no",topic=41 -> "If you ever aquire a parchment with lizard writings, bring it here." +"yes",topic=41,Count(Type) "What are you talking about? You own no parchment with lizard writings at all." +"yes",topic=41,Count(Type)>=Amount -> "Thank you in the name of science.", Delete(Type), CreateMoney + +###### + + +######################## Missionen + +"mission",QuestValue(300)>0,QuestValue(300)<4 -> "The missions available for your rank are the butterfly hunt, plant collection and ice delivery." + +"butterfly", "hunt",QuestValue(300)>0,QuestValue(304)=0 -> "The mission asks you to collect some species of butterflies, are you interested?", topic=5 +"no", topic=5 -> "Perhaps another mission suits you more." +"yes", topic=5 -> "This preparation kit will allow you to collect a purple butterfly you have killed ...", "Just use it on the fresh corpse of a purple butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,1) + +"butterfly", "hunt",QuestValue(304)=1 -> "Did you acquire the purple butterfly we are looking for?",Type=4865,Amount=1, topic=6 +"no", topic=6 -> "Then go and look for one." +"yes", topic=6,Count(Type) "I can't see a purple butterfly. Perhaps you lost it somewhere." +"yes", topic=6,Count(Type)>=Amount -> "A little bit battered but it will do. Thank you! If you think you are ready, ask for another butterfly hunt.", Delete(Type), SetQuestValue(304,2) + + +"butterfly", "hunt",QuestValue(304)=2 -> "This preparation kit will allow you to collect a blue butterfly you have killed ...", "Just use it on the fresh corpse of a blue butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,3) + +"butterfly", "hunt",QuestValue(304)=3 -> "Did you acquire the blue butterfly we are looking for?",Type=4866,Amount=1,topic=7 +"no",topic=7 -> "Then go and look for one." +"yes",topic=7,Count(Type) "I can't see a blue butterfly. Perhaps you lost it somewhere." +"yes",topic=7,Count(Type)>=Amount -> "Again I think it will do. Thank you! If you think you are ready, ask for another butterfly hunt.", Delete(Type), SetQuestValue(304,4) + + +"butterfly", "hunt",QuestValue(304)=4 -> "This preparation kit will allow you to collect a red butterfly you have killed ...", "Just use it on the fresh corpse of a red butterfly, return the prepared butterfly to me and give me a report of your butterfly hunt.", Create(4863), SetQuestValue(304,5) + +"butterfly", "hunt",QuestValue(304)=5 -> "Did you acquire the red butterfly we are looking for?",Type=4864,Amount=1, topic=8 +"no", topic=8 -> "Then go and look for one." +"yes", topic=8,Count(Type) "I can't see a red butterfly. Perhaps you lost it somewhere." +"yes", topic=8,Count(Type)>=Amount -> "That is an extraordinary species you have brought. Thank you! That was the last butterfly we needed.", Delete(Type), SetQuestValue(304,6),SetQuestValue(300,QuestValue(300)+1) +"butterfly", "hunt",QuestValue(304)=6 -> "You have already finished the butterfly hunt. Of course you can always ask me what missions are available." + +#### topic 9 ist verwendet + +"plant", "collection",QuestValue(300)>0,QuestValue(305)=0 -> "In this mission we require you to get us some plant samples from Tiquandan plants. Would you like to fulfil this mission?", topic=10 +"no", topic=10 -> "Perhaps another mission suits you more." +"yes", topic=10 -> "Fine! Here take this botanist's container. Use it on a jungle bells plant to collect a sample for us. Report about your plant collection when you have been successful.", Create(4867),SetQuestValue(305,1) + +"plant", "collection",QuestValue(305)=1 -> "Did you acquire the sample of the jungle bells plant we are looking for?",Type=4868,Amount=1, topic=11 +"no", topic=11 -> "Then go and look for one." +"yes", topic=11,Count(Type) "Sorry, you don't have a useful sample." +"yes", topic=11,Count(Type)>=Amount -> "I see. It seems you've got some quite useful sample by sheer luck. Thank you! Just tell me when you are ready to continue with the plant collection.", Delete(Type),SetQuestValue(305,2) + +"plant", "collection",QuestValue(305)=2 -> "Use this botanist's container on a witches cauldron to collect a sample for us. Bring it here and report about your plant collection.", Create(4867),SetQuestValue(305,3) + +"plant", "collection",QuestValue(305)=3 -> "Did you acquire the sample of the witches cauldron we are looking for?",Type=4870,Amount=1, topic=12 +"no", topic=12 -> "Then go and look for one." +"yes", topic=12,Count(Type) "Sorry, you don't have any useful sample." +"yes", topic=12,Count(Type)>=Amount -> "Ah, finally. I started to wonder what took you so long. But thank you! Another fine sample, indeed. Just tell me when you are ready to continue with the plant collection.", Delete(Type),SetQuestValue(305,4) + +"plant", "collection",QuestValue(305)=4 -> "Use this botanist's container on a giant jungle rose to obtain a sample for us. Bring it here and report about your plant collection.", Create(4867),SetQuestValue(305,5) + +"plant", "collection",QuestValue(305)=5 -> "Did you acquire the sample of the giant jungle rose we are looking for?",Type=4869,Amount=1, topic=13 +"no", topic=13 -> "Then go and look for one. Keep in mind we need samples of the giant jungle rose, not the small one." +"yes", topic=13,Count(Type) "Sorry you don't have any useful sample. Perhaps you did not use the botanist's container on a giant jungle rose as requested, but on a small one." +"yes", topic=13,Count(Type)>=Amount -> "What a lovely sample! With that you have finished your plant collection missions.", Delete(Type),SetQuestValue(305,6),SetQuestValue(300,QuestValue(300)+1) + + +"plant", "collection",QuestValue(305)=6 -> "You have already finished the plant collection missions. Of course you can always ask me what missions are available." + +"ice", "delivery",QuestValue(300)>0,QuestValue(306)=0 -> "Our finest minds came up with the theory that deep beneath the ice island of Folda ice can be found that is ancient. To prove this theory we would need a sample of the aforesaid ice ...", "Of course the ice melts away quickly so you would need to hurry to bring it here ...", "Would you like to accept this mission?",topic=14 + +"no",topic=14 -> "Perhaps another mission suits you more." +"yes",topic=14 -> "So listen please: Take this ice pick and use it on a block of ice in the caves beneath Folda. Get some ice and bring it here as fast as you can ...", "Should the ice melt away, report on your ice delivery mission anyway. I will then tell you if the time is right to start another mission.", Create(4872),SetQuestValue(306,1) +"ice", "delivery",QuestValue(306)=1 -> "Did you get the ice we are looking for?",Type=4837,Amount=1,topic=15 +"no",topic=15 -> "Did it melt away?",topic=16 + "no",topic=16 -> "Then don't waste your time and go to the caves on Folda to get some ice." + "yes",topic=16,QuestValue(307)=0 -> "I think you are wrong, just try to get that ice as you were told." + "yes",topic=16,QuestValue(307)=1,Count(Type)>Amount -> "What are you talking about, I can see you still have some ice on you." + "yes",topic=16,QuestValue(307)=1,Count(Type) "*Sigh* I think the time is right to grant you another chance to get that ice. Hurry up this time.",SetQuestValue(307,0), EffectOpp(13) + +"yes",topic=15,Count(Type) "Sorry, you don't have any ice on you." +"yes",topic=15,Count(Type)>=Amount,QuestValue(307)=0 -> "This ice looks odd. I don't think it's from Folda. Make sure to travel there to get the ice we are looking for." +"yes",topic=15,Count(Type)>=Amount,QuestValue(307)=1 -> "Just in time. Sadly not much ice is left over but it will do. Thank you again.",SetQuestValue(306,2),SetQuestValue(300,QuestValue(300)+1), Delete(Type) + +"ice", "delivery",QuestValue(306)=2 -> "You already brought us the ice sample that we needed. Of course you can always ask me what missions are available." + +######### +# Debug # +######### + +#"advancement", QuestValue(300)>0, QuestValue(304)<6 -> "You have not even finished your butterfly hunt. Perform some missions and ask again." +#"advancement", QuestValue(300)>0, QuestValue(304)=6 -> "So you think you are worthy of an advancement?",topic=55 +#"no",topic=55 -> "Then stop bothering me with it." +#"yes",topic=55, QuestValue(305)<6 -> "You have not even finished your plant collection. Perform some missions and ask again." +#"yes",topic=55, QuestValue(305)=6 -> "Well, you performed some simple tasks at least but do you realy think you deserve an advancement?",Topic=56 +#"no",topic=56 -> "Then stop bothering me with it." +#"yes",topic=56, QuestValue(306)<2 -> "First finish the ice delivery mission. Then we might talk about advancement." +#"yes",topic=56, QuestValue(306)=2, QuestValue(300)>3 -> "Sorry but your rank mirrors your performance perfectly." +#"yes",topic=56, QuestValue(306)=2, QuestValue(300)<4 -> "This is odd indeed. I must have confused something in my papers, sorry. I grant you the rank of a journeyman.",SetQuestValue(300,4) + + +######################## +# NEUE MISSIONEN (4-6) # +######################## + + +"mission",QuestValue(300)>3,QuestValue(300)<7 -> "The missions available for your rank are lizard urn, beholder secrets and orc powder." + +"lizard", "urn",QuestValue(308)=0,QuestValue(300)>3 -> "The explorer society would like to acquire an ancient urn which is some sort of relic to the lizard people of Tiquanda. Would you like to accept this mission?",topic=17 + +"no",topic=17 -> "Perhaps another mission suits you more." +"yes",topic=17 -> "You have indeed the spirit of an adventurer! In the south-east of Tiquanda is a small settlement of the lizard people ...", "Beneath the newly constructed temple there, the lizards hide the said urn. Our attempts to acquire this item were without success ...", "Perhaps you are more successful.",SetQuestValue(308,1) + +"lizard", "urn",QuestValue(308)=1 -> "Did you manage to get the ancient urn?",Type=4847,Amount=1,topic=18 + +"no",topic=18 -> "It must be somewhere beneath the newly constructed lizard temple in the south-east of Tiquanda." +"yes",topic=18,Count(Type) "Sorry, you don't have the urn. It has to be somewhere beneath the newly constructed lizard temple in the south-east of Tiquanda." +"yes",topic=18,Count(Type)>=Amount -> "Yes, that is the prized relic we have been looking for so long. You did a great job, thank you.", Delete(Type),SetQuestValue(308,2),SetQuestValue(300,QuestValue(300)+1) + +"lizard", "urn",QuestValue(308)=2 -> "You already retrieved the urn for us. Of course you can always ask me what missions are available." + +###################### + +"beholder", "secret",QuestValue(309)=0,QuestValue(300)>3 -> "We want to learn more about the ancient race of beholders. We believe the black pyramid north east of Darashia was originally built by them ...", "We ask you to explore the ruins of the black pyramid and look for any signs that prove our theory. You might probably find some document with the numeric beholder language ...", "That would be sufficient proof. Would you like to accept this mission?",topic=19 + +"no",topic=19 -> "Perhaps another mission suits you more." +"yes",topic=19 -> "Excellent! So travel to the city of Darashia and then head north-east for the pyramid ...", "If any documents are left, you probably find them in the catacombs beneath. Good luck!",SetQuestValue(309,1) + +"beholder", "secret",QuestValue(309)=1 -> "Have you found any proof that the pyramid was built by beholders?",Type=4846,Amount=1,topic=20 +"no",topic=20 -> "We are sure there is some document left. Probably in the deepest catacombs beneath the black pyramid." +"yes",topic=20,Count(Type) "Sorry, whatever you have found is no true proof for our theory. Please return to the black pyramid and explore it carefully." +"yes",topic=20,Count(Type)>=Amount -> "You did it! Excellent! The scientific world will be shaken by this discovery!", Delete(Type),SetQuestValue(309,2),SetQuestValue(300,QuestValue(300)+1) + + +"beholder", "secret",QuestValue(309)=2 -> "You already recovered the scroll. Of course you can always ask me what missions are available." + +##################### + +"orc", "powder",QuestValue(310)=0,QuestValue(300)>3 -> "It is commonly known that orcs of Uldereks Rock use some sort of powder to increase the fierceness of their war wolves and berserkers ...", "What we do not know are the ingredients of this powder and its effect on humans ...", "So we would like you to get a sample of the aforesaid powder. Do you want to accept this mission?", topic=21 + +"no",topic=21 -> "Perhaps another mission suits you more." +"yes",topic=21 -> "You are a brave soul. As far as we can tell, the orcs maintain some sort of training facility in some hill in the north-east of their city ...", "There you should find lots of their war wolves and hopefully also some of the orcish powder. Good luck!",SetQuestValue(310,1) + +"orc", "powder",QuestValue(310)=1 -> "Did you acquire some of the orcish powder?",Type=4838,Amount=1,topic=34 +"no",topic=34 -> "Make sure to search in the hill they use to raise and train their war wolves." +"yes",topic=34,Count(Type) "Sorry, you have nothing with you that would fit the descriptions we got of the powder." +"yes",topic=34,Count(Type)>=Amount -> "You really got it? Amazing! Thank you for your efforts.", Delete(Type),SetQuestValue(310,2),SetQuestValue(300,QuestValue(300)+1) +"orc", "powder",QuestValue(310)=2 -> "You already brought us some orcish powder. Of course you can always ask me what missions are available." + +########################## +# NEUE MISSIONEN (7-9) # +########################## + +"mission",QuestValue(300)>6,QuestValue(300)<10 -> "The missions available for your rank are elven poetry, memory stone and rune writings." + +"elven", "poetry",QuestValue(311)=0,QuestValue(300)>6 -> "Some high ranking members would like to study elven poetry. They want the rare book 'Songs of the Forest' ...", "For sure someone in Ab'Dendriel will own a copy. So you would just have to ask around there. Are you willing to accept this mission?",topic=22 +"no",topic=22 -> "Perhaps another mission suits you more." +"yes",topic=22 -> "Excellent. This mission is easy but nonetheless vital. Travel to Ab'Dendriel and get the book.",SetQuestValue(311,1) + + +"song", "forest",QuestValue(311)=1 -> "Did you acquire a copy of 'Songs of the Forest' for us?",Type=4844,Amount=1,topic=23 +"elven", "poetry",QuestValue(311)=1 -> * +"book",QuestValue(311)=1 -> * +"no",topic=23 -> "Then try harder. Someone might own it. If you lost a copy, ask around again." +"yes",topic=23,Count(Type) "Whatever you thought you acquired, it's not the book we are looking for! If you lost the copy, ask around again." +"yes",topic=23,Count(Type)>=Amount,QuestValue(312)=0 -> "It can easily be seen that this book was forged by a dwarf. Try to get a real copy somewhere." +"yes",topic=23,Count(Type)>=Amount,QuestValue(312)=1 -> "Let me have a look! Yes, that's what we wanted. A copy of 'Songs of the Forest'. I won't ask any questions about those bloodstains.", Delete(Type),SetQuestValue(311,2),SetQuestValue(300,QuestValue(300)+1) + +"elven", "poetry",QuestValue(311)=2 -> "You already pleased our leadership by acquiring a copy of that book. Of course you can always ask me what missions are available." + +########################## + +"memory", "stone",QuestValue(313)=0,QuestValue(300)>6 -> "We acquired some knowledge about special magic stones. Some lost civilisations used it to store knowledge and lore, just like we use books ...", "The wisdom in such stones must be immense, but so are the dangers faced by every person who tries to obtain one...", "As far as we know the ruins found in the north-west of Edron were once inhabited by beings who used such stones. Do you have the heart to go there and to get us such a stone?",topic=24 +"no",topic=24 -> "Perhaps another mission suits you more." +"yes",topic=24 -> "In the ruins of north-western Edron you should be able to find a memory stone. Good luck.",SetQuestValue(313,1) + + +"memory", "stone",QuestValue(313)=1 -> "Were you able to acquire a memory stone for our society?",Type=4841,Amount=1,topic=25 +"no",topic=25 -> "Try harder. We are sure there are memory stones left in the north-western dungeons of Edron." +"yes",topic=25,Count(Type) "You don't have any memory stone!" +"yes",topic=25,Count(Type)>=Amount,QuestValue(314)=0 -> "This memory stone looks damaged. Probably you bought it from some suspicious individual. Travel to Edron and get one on your own." +"yes",topic=25,Count(Type)>=Amount,QuestValue(314)=1 -> "A flawless memory stone! Incredible! It will take years even to figure out how it works but what an opportunity for science, thank you!", Delete(Type),SetQuestValue(313,2),SetQuestValue(300,QuestValue(300)+1) + + +"memory", "stone",QuestValue(313)=2 -> "You already brought us a memory stone. It will take years to make it work and probably decades to decipher and understand the knowledge the stone contains." + +######################### + +"rune", "writing",QuestValue(315)=0,QuestValue(300)>6 -> "We would like to study some ancient runes that were used by the lizard race. We suspect some relation of the lizards to the founders of Ankrahmun ...", "Somewhere under the ape infested city of Banuta, one can find dungeons that were once inhabited by lizards ...", "Look there for an atypical structure that would rather fit to Ankrahmun and its tombs. Copy the runes you will find on this structure ...", "Are you up to that challenge?",topic=26 +"no",topic=26 -> "Perhaps you are interested at another time." +"yes",topic=26 -> "Excellent! Here, take this tracing paper and use it on the object you will find there to create a copy of the ancient runes.", Create(4842),SetQuestValue(315,1) + +"rune", "writing",QuestValue(315)=1 -> "Did you create a copy of the ancient runes as requested?",Type=4843,Amount=1,topic=27 +"no",topic=27 -> "Please remember, somewhere in the dungeons beneath Banuta has to be some structure that seemingly does not belong there. Use the copy paper on it." +"yes",topic=27,Count(Type) "You don't have any copy of the runes that we need!" +"yes",topic=27,Count(Type)>=Amount,QuestValue(316)=0 -> "This copy is ruined by sweat and blood, sorry. Travel to Banuta and make sure to make a clean copy." +"yes",topic=27,Count(Type)>=Amount,QuestValue(316)=1 -> "It's a bit wrinkled but it will do. Thanks again.", Delete(Type),SetQuestValue(315,2),SetQuestValue(300,QuestValue(300)+1) + +"rune", "writing",QuestValue(315)=2 -> "You already brought us a copy of those runes which gave us some interesting insights." + +######################## + +"mission",QuestValue(300)=10,QuestValue(317)=0 -> "The explorer society needs a great deal of help in the research of astral travel. Are you willing to help?", topic=28 +"no", topic=28 -> "Perhaps you are interested at another time" +"yes" , topic=28 -> "Fine. The society is looking for new means to travel. Some of our most brilliant minds have some theories about astral travel that they want to research further ...", "Therefore we need you to collect some ectoplasm from the corpse of a ghost. We will supply you with a collector that you can use on the body of a slain ghost ...", "Do you think you are ready for that mission?", topic=29 +"no", topic=29 -> "Perhaps you are interested some other time." +"yes", topic=29 -> "Good! Take this container and use it on a ghost that was recently slain. Return with the collected ectoplasm and hand me that container ...", "Don't lose the container. They are expensive!", Create(4852),SetQuestValue(317,1) + +"ectoplasm",QuestValue(317)=1 -> "Do you have some collected ectoplasm with you?",Type=4853,Amount=1,topic=30 +"container",QuestValue(317)=1 -> * +"mission",QuestValue(317)=1 -> * +"no",topic=30 -> "Just use the container on a slain ghost. Make sure it lost its unholy mockery of life before you fulfil your task. Be quick, a partly decomposed corpse will not contain enough ectoplasm." +"yes",topic=30,Count(Type) "Sorry, but you have no ectoplasm at all!" +"yes",topic=30,Count(Type)>=Amount -> "Phew, I had no idea that ectoplasm would smell that ... oh, it's you, well, sorry. Thank you for the ectoplasm.", Delete(Type),SetQuestValue(317,2),SetQuestValue(300,11) + +"ectoplasm",QuestValue(317)=2 -> "You already brought us enough ectoplasm for our research." + +######################### + +"mission",QuestValue(300)=11,QuestValue(318)=0 -> "The research on ectoplasm makes good progress. Now we need some spectral article. Our scientists think a spectral dress would be a perfect object for their studies ...", "The bad news is that the only source to got such a dress is the queen of the banshees. Do you dare to seek her out?",topic=31 + +"no",topic=31 -> "Perhaps you are interested in it some other time." +"yes",topic=31 -> "That is quite courageous. We know, it's much we are asking for. The queen of the banshees lives in the so called Ghostlands, south west of Carlin. It is rumoured that her lair is located in the deepest dungeons beneath that cursed place ...", "Any violence will probably be futile, you will have to negotiate with her. Try to get a spectral dress from her. Good luck.",SetQuestValue(318,1) + +"spectral", "dress",QuestValue(318)=1 -> "Have you acquired the spectral dress we need?",Type=4836,Amount=1,topic=32 +"mission",QuestValue(318)=1 -> * + +"no", topic=32 -> "Remember, the queen of the banshees lives in the so called Ghostlands, south west of Carlin. Her lair is rumoured to be in the deepest dungeons beneath that cursed place ...", "Any violence will probably be futile and you will have to negotiate with her. Try to get a spectral dress from her." +"yes", topic=32,Count(Type) "Sorry, you don't have a spectral dress!" +"yes", topic=32,Count(Type)>=Amount,QuestValue(319)=0 -> "Sorry, this dress is infested with spectral moths. Get a spectral dress from the banshee queen." +"yes", topic=32,Count(Type)>=Amount,QuestValue(319)=1 -> "Just in time! With this spectral article we can start the final phase of our research.", Delete(Type),SetQuestValue(318,2),SetQuestValue(300,12) + +"spectral",QuestValue(318)=2 -> "You provided us with a spectral dress and we are ready for the final stages of our research project." + + +##################### + +"portals",QuestValue(320)=5 -> "Sorry, you did not charge both floor tiles as requested." +"mission",QuestValue(320)=5 -> * + +"portals",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=0 -> Amount=6, Type=5021,"Both carvings are now charged and harmonised. In theory you should be able to travel in zero time from one base to the other ...", "However, you will need to have an orichalcum pearl in your possession to use it as power source. It will be destroyed during the process. I will give you 6 of such pearls and you can buy new ones in our bases ...", "In addition, you need to be a premium explorer to use the astral travel ...", "And remember: it's a small teleport for you, but a big teleport for all Tibians.",SetQuestValue(323,1), Create(Type) +"mission",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=0 -> * + +"portals",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=1 -> "The portals should be ready to be used now. If you have an orichalcum pearl with you, enter the portal." +"mission",QuestValue(320)=5,QuestValue(321)=1,QuestValue(322)=1,QuestValue(323)=1 -> "There are no new missions avaliable right now." +###################### +"orichalcum" -> Amount=1, Type=5021, Price=80, "Do you want to buy an orichalcum pearl for %P gold?", Topic=51 + +Topic=51,"yes",CountMoney>=Price -> "Here, use it wisely.", DeleteMoney, Create(Type) +Topic=51,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=51 -> "Is there anything else I can do for you?" + +%1,1<%1,"orichalcum" -> Amount=%1, Type=5021, Price=80*%1, "Do you want to buy %A orichalcum pearls for %P gold?", Topic=52 + +Topic=52,"yes",CountMoney>=Price -> "Here, use them wisely.", DeleteMoney, Create(Type) +Topic=52,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=52 -> "Is there anything else I can do for you?" + +###################### + +"botanist", "container" -> Amount=1, Type=4867, Price=500, "Do you want to buy a botanist container for %P gold?", Topic=56 +"preparation", "kit" -> Amount=1, Type=4863, Price=250, "Do you want to buy a preparation kit for %P gold?", Topic=56 +"ectoplasm", "container" -> Amount=1, Type=4852, Price=750, "Do you want to buy an ectoplasm container for %P gold?", Topic=56 + +Topic=56,"yes",CountMoney>=Price -> "Here, better don't lose it.", DeleteMoney, Create(Type) +Topic=56,"yes" -> "Sorry, you don't have the money for this transaction." +Topic=56 -> "Is there anything else I can do for you?" + diff --git a/data/npc/fahradin.npc b/data/npc/fahradin.npc new file mode 100644 index 0000000..be19739 --- /dev/null +++ b/data/npc/fahradin.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fahradin.npc: Datenbank für den Maridzauberer Fa'hradin + +Name = "Fa'hradin" +Outfit = (80,0-0-0-0) +Home = [33106,32541,5] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Aaaah... what have we here. A human - interesting. And such an ugly specimen, too... All right, human %N. How can I help you?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Wait human. I'll take care of you in a minute, %N.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell, human." + +"bye" -> "Farewell, human. I will always remember you. Unless I forget you, of course.", Idle +"farewell" -> * +"name" -> "I am known as Fa'hradin." +"fa'hradin" -> "Yes, that is me. It seems you have heard my name before." +"job" -> "Well, you could say I am the wizard of the Marid. Of course, I know that all djinn are magical creatures. But let us put it this way: I am slightly better at wielding magic then your average djinn in the street." +"djinn" -> "Our race is in a deplorable state at the moment. However, it is interesting from a scientific point of view. I am really curious to see if the Efreet and the Marid are really going to develop into two completely different species..." +"efreet" -> "I have not be been able to figure out exactly why the Efreet have developed a different skin colour. ...", + "This poses an interesting scientific problem, you know. Perhaps it is a magical effect, but I have a feeling that there are other forces at work here." +"marid" -> "That is what we call ourselves. We like to think of ourselves as the true inheritors of the djinn legacy." + +"gabel" -> "He is our leader. He does have his mistakes, but then he always tries to do what he thinks is right, and I suppose that makes him a good leader." +"king" -> "Djinns do not have kings. Gabel has long abdicated the title because of his convictions, and Malor... Well, I suppose he would not refuse to take the crown, but I doubt he will ever get a chance to do so." +"malor" -> "That treacherous snake has been waiting for a chance to seize power for as long as I can remember. He and Gabel used to be as close as brothers, you know." +"mal'ouquah",QuestValue(281)=0 -> "Mal'ouquah is Malor's fortress which lies to the south. I know it well even though I have never been there myself. Insider information, you know." +"mal'ouquah",QuestValue(281)>0 -> "Mal'ouquah is Malor's fortress which lies to the south. Of course you cannot enter it through the front door. But there's also an unguarded back door in the north-west corner of the fortress..." +"ashta'daramai" -> "That is what this place is called. It is not difficult to guess that that name was not my idea." +"human" -> "You are a curious species: Weak, yet strong. Stupid, yet clever. Evil, yet good. Fascinating, really. ...", + "For thousands of years we regarded the northern continent as barbaric and wild. ...", + "And all of sudden there are roads and pastures and mighty cities. The problem with us djinn is that we always underestimate other species, especially humans." +"zathroth" -> "He created our race, but we find it hard to love him. Sometimes I think that whole war has erupted because there is something like a design flaw in us djinns, an inconsistency in the way we are. ...", + "I have never been able to put my finger on it, but it keeps me wondering..." +"tibia" -> "Eons ago when I was still young I felt the world was a place of wonder and joy. Now all I see is a badly working system full of design flaws. ...", + "Must have been the first world the gods have created. Who knows? Perhaps they have learnt from their mistakes, and they are creating a better world somewhere else?" +"daraman" -> "I have met him myself. He was a sharp thinker and a charismatic conversationalist. ...", + "I suppose he never managed to convince me quite as thoroughly as he managed to convince Gabel, but I came to admire his amazing personal integrity. ...", + "In the end I chose to follow his creed because I felt that we djinn lacked something, and I thought that perhaps Daraman had an answer." +"darashia" -> "Darashia is comparatively young. The local ruler managed to establish his own little caliphate thanks to the riches he accumulated. ...", + "As long as he continues to control the eastern trade routes Darashia will continue to flourish." +"scarab" -> "An interesting species. Oh, they are as thick as two short planks, of course, but there is definitely something magic about them. ...", + "I have not carried out many studies, however, because dissecting scarabs is a real hassle." +"edron" -> "I am not much of a traveller, but I would like to see the northern cities everbody is talking about. Perhaps one day I will do that. Oh, I will use some kind of magical disguise, of course." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That is one of the oldest human settlements in the whole of Tibia. I understand it is currently ruled by the pharaoh - some sort of undead priest-king. I am sure that must be a charming fellow." +"pharaoh" -> "Apparently the whole issue of dying in order to extend the natural life span was his idea. Those humans. You never know what they come up with next!" + +"palace" -> "The pharaoh's palace in Ankrahmun is an impressive building. At least that is how I remember it to be. ...", + "I suppose it is a little less cheerful these days, with all that undead riff-raff roaming its halls." +"ascension" -> "A fundamental part of the pharaoh's cult. I have not studied it in any detail, though." +"rah" -> "Another cornerstone of the undead pharaoh's theological theories. I do not know much more about it, I'm afraid." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "None of the mountains here has developed naturally. The whole range has been raised by using powerful magic, and a lot of this magic lingers to this very day. A great place to be a wizard, but a dangerous place to travel." +"kha'labal" -> "The Kha'labal used to be a paradise. I remember it well. The fact that it is a barren desert today might give you an idea of the things that happened during the war." +"war" -> "For a long time it seemed that the war was over for good. But now that Malor is free again he will surely kindle the flame of war again. ...", + "Damn that foolish orc. If I manage to get hold of him he will be turned into something much worse than a slime." +"melchior" -> "Ah. I remember him. A trader, was he not? I haven't seen him for a long time." +"alesar" -> "That name brings up bad memories. I never really liked him, but you just had to admire his skills at the forge. His desertion was a great loss for our cause." +"baa'leal" -> "Oh, you just have to love that djinn. We have met on the battlefield on half a dozen occasions, and he lost each single one of these battles. ...", + "To be sure, he is a great warrior, but he is also lousy general and a complete dunce. As long as he is Malor's commander-in-chief I think we're safe." +"lamp" -> "Ah yes. We djinn sleep in lamps. We have a natural ability to dematerialise, you see." +"rata'mari" -> "Ah yes. Have you seen him? One of my best works so far. Nobody will ever suspect he is in fact a transformed djinn. The only problem is I'm much better with transforming people into other forms than with transforming them back. Poor fellow." +"fa'hradin","lamp" -> "I hate to flatter myself, but that lamp was a masterpiece. Malor would have been imprisoned in it for the rest of his miserable life if it had not been for that nincompoop who calls himself an orc king. That foolish troglodyte!" + +"work",QuestValue(280)<2 -> "Looking for work, are you? Well, it's very tempting, you know, but I'm afraid we do not really employ beginners. Perhaps our cook could need a helping hand in the kitchen." +"mission",QuestValue(280)<2 -> * + +"work",QuestValue(280)=2,QuestValue(281)=0 -> "I have heard some good things about you from Bo'ques. But I don't know. ...", + "Well, all right. I do have a job for you. ...", + "In order to stay informed about our enemy's doings, we have managed to plant a spy in Mal'ouquah. ...", + "He has kept the Efreet and Malor under surveillance for quite some time. ...", + "But unfortunately, I have lost contact with him months ago. ...", + "I do not fear for his safety because his cover is foolproof, but I cannot contact him either. This is where you come in. ...", + "I need you to infiltrate Mal'ouqhah, contact our man there and get his latest spyreport. The password is PIEDPIPER. Remember it well! ...", + "I do not have to add that this is a dangerous mission, do I? If you are discovered expect to be attacked! So good luck, human!", SetQuestValue(281,1) +"bo'ques",QuestValue(280)=2,QuestValue(281)=0 -> * +"mission",QuestValue(280)=2,QuestValue(281)=0 -> * + +"work",QuestValue(281)=1 -> "Did you already retrieve the spyreport?", Topic=1 +"mission",QuestValue(281)=1 -> * +"report",QuestValue(281)=1 -> * +"spy",QuestValue(281)=1 -> * + +Topic=1,"yes",Count(3232)=1,! -> "You really have made it? You have the report? How come you did not get slaughtered? I must say I'm impressed. Your race will never cease to surprise me. ...", + "Well, let's see. ...", + "I think I need to talk to Gabel about this. I am sure he will know what to do. Perhaps you should have a word with him, too.", Amount=1, Delete(3232), SetQuestValue(281,2) +Topic=1 -> "Don't waste any more time. We need the spyreport of our man in Mal'ouquah as soon as possible! ...", + "Also don't forget the password to contact our man: PIEDPIPER!" + +"work",QuestValue(281)=2 -> "Did you already talk to Gabel about the report? I think he will have further instructions for you." +"mission",QuestValue(281)=2 -> * +"report",QuestValue(281)=2 -> * +"spy",QuestValue(281)=2 -> * +} diff --git a/data/npc/falk.npc b/data/npc/falk.npc new file mode 100644 index 0000000..c9da165 --- /dev/null +++ b/data/npc/falk.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# falk.npc: Datenbank für den Wachmann/Fremdenführer Falk + +Name = "Falk" +Outfit = (131,76-11-11-76) +Home = [33190,31777,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "LONG LIVE KING TIBIANUS! Welcome to the isle of Edron!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I am busy?!." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "Sorry, that's confidential." +"how","are","you"-> "I'm well prepared for my duty." +"sell" -> "Visit the shopkeepers to buy their fine wares." +"king" -> "LONG LIVE THE KING!" +"leader" -> * +"name" -> "I'm Lieutenant Falk." +"job" -> "I'm the Edron harbour guard. I protect Edron castle and inform visitors about this building and Edron itself." +"army" -> "The local army consists only of the Knights of Banor's Blood." +"guard" -> * +"battlegroup" -> "There are the Dogs of War, the Red Guards, and the Silver Guards." +"castle" -> "The Edron castle is the home of many shops, a tavern, a bank, a depot, a post office and the temple of Banor's blood." +"subject" -> "We all live under the benevolent guidance of our king." +"shop" -> "The shops are on the eastern side of the castle. Upstairs you'll find a tailor, a blacksmith, and an equipment store." +"tavern" -> "The tavern is called the Horn of Plenty, and it's located upstairs in the southwest corner of the castle." +"bank" -> "You'll find the bank in the southwest of the castle. Look for Ebenizer, you can't miss him." +"post" -> "The post office, run by the lovely Chrystal is in the southwest corner of the castle, near the Royal Bank." +"temple" -> "The temple can be found underground, in the southeast corner of the castle. There you can become a citizen of Edron" +"citizen" -> * +"Edron" -> "The mysterious isle has many secrets and sights outside the castle. The areas of interest are in the west, the southwest, the north, and the northwest." +"southwest" -> "There are rumours of orc buildings in the southeast. They say some daring fellows found a passage to this area in an old cavern beneath the Edron flats." +"northwest" -> "Don't even think about going there. Renegade Knights of Banor's Blood went there to unearth forbidden secrets in an ancient ruin." +"north" -> "In the north, there is an ancient city of cyclopses, called the cyclopolis. They are wary of us, but trade with servants of evil from any known race." +"west" -> "There are rumours of two tribes of minor monsters who battle each other for dominance over the area. Not worth to crawl the sewers to get there." +"city" -> "In the city there is a furniture store and a jeweller. The Noodles Academy and the cemetary are outside." +"academy" -> "The Noodles Academy of the magic arts is in the east of Edron city." +"cemetary" -> "The cemetary is north of the hamlet of Stonehome, which is at the east coast, northeast of Edron city. The cemetary is rumoured to be haunted." +"work" -> "Explore the isle and destroy any enemy forces encountered. The honor shall be your reward." +"mission" -> * +"quest" -> * + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/data/npc/faluae.npc b/data/npc/faluae.npc new file mode 100644 index 0000000..e5a289a --- /dev/null +++ b/data/npc/faluae.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# faluae.npc: Datenbank für die Deraisim-Anführerin Faluae (Elfenstadt) + +Name = "Faluae" +Outfit = (62,0-0-0-0) +Home = [32660,31673,7] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am busy now." +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the spokesperson of the Deraisim caste." +"name" -> "I am known as Faluae Ethrathil." +"time" -> "Sorry, I don't know." + +"carlin" -> "For a human city Carlin is not that bad. Its still a scar in the natural enviroment of course." +"thais" -> "Thais is fa away and that is a good thing." +"venore" -> "I don't appreciate that my people buy that much human wares but there is little I can do about it." +"roderick" -> "He is the thaian ambassador." +"olrik" -> "He is the assistant of the thaian ambassador." + +"king" -> "I know nothing about kings." +"elves" -> "The elves are split in three castes: the Deraisim, the Kuridai, and the Cenath." +"dwarfs" -> "Funny little people sometimes. But their tunnels are harmful to the enviroment." +"humans" -> "I don't like their stone cities and their stench." +"troll" -> "I still can't stand the thought of the Kuridai keeping these disgusting creatures in our settlement." +"army" -> "I don't understand what you mean." +"cenath" -> "They think they are so wise but they lost the ability to adore the simple things." +"kuridai" -> "They are paranoid, what makes them so aggressive." +"deraisim" -> "We only stay here to keep our people together. We hunt for them, provide them with food, and scout the area." +"abdaisim" -> "Our lost brothers and sisters. Oh, we miss them so much." +"teshial" -> "I can only hope they will return one day." +"ferumbras" -> "I hope this fallen servant of evil will never find us." +"crunor" -> "I praise the Great Tree and Mother Earth, who gave birth to all life, and Nera, the celestial paladin." +"nera" -> "The lady of spring, helper of Crunor and Earth itself." +"excalibug" -> "What is that?" +"news" -> "Sorry, the only news I have concern the growth of plants and the coming and going of animal herds." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"magic" -> "I learned a little about some minor spells." +"druid" -> "Druids are very close to Crunor." +"sorcerer" -> "They are so ... destructive." +"spellbook" -> "Sorry, I have none on me." +"spell" -> "I could teach you the spells of 'Light', 'Great Light', 'Food', and 'Find Person'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +"find","person" -> "I'm sorry, but this spell is only for druids and paladins." +"light" -> * +"food" -> * +"great","light" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "I thought so." +} diff --git a/data/npc/feizuhl.npc b/data/npc/feizuhl.npc new file mode 100644 index 0000000..21ba2cc --- /dev/null +++ b/data/npc/feizuhl.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# feizuhl.npc: Möbelverkäufer Feizuhl in Ankrahmun + +Name = "Feizuhl" +Outfit = (133,98-116-43-95) +Home = [33066,32886,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, mourned %N.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell furniture both to the mourned and the enlightened." +"shop" -> * +"name" -> "I'm the mourned Feizuhl, pilgrim." +"time" -> "It is %T right now." +"thanks" -> "It was a pleasure, pilgrim." +"thank","you" -> * + +"darama" -> "This is the continent of my birth, my death and also of my ascension, if I learn enough in my mortal days." +"darashia" -> "If they would only see the light and follow the way of ascension. Thrice mourned be they." +"daraman" -> "The false prophet lead his people into damnation. Mourned shall he be." +"ankrahmun" -> "Our city is old. Older even then our beloved pharaoh." +"city" -> * + +"pharaoh" -> "Our pharaoh holds the key to our ascension. Praised be our pharaoh." +"arkhothep" -> * +"mortality" -> "Only if we leave mortality behind will we attain ascension." + +"ascension" -> "The ascension to salvation and perhaps even to divine status." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the union of the three parts that were never meant to be bound together." +"Akh" -> "The Akh is our vulnerable, ageing flesh." + +"undead" -> "Undeath is an important step towards ascension." +"undeath" -> * +"Rah" -> "The Rah is our spiritual essence." +"uthun" -> "The Uthun is our memory. Call it personality if you like." +"mourn" -> "We are mortals and thus to be mourned, for while we are trapped in this frail form we are excluded from enlightenment and ascension." + +"arena" -> "Look for the arena in the heart of our city." +"palace" -> "The palace is located in the centre of our city, south of the arena." +"temple" -> "Look for the temple in the south-eastern section of Ankrahmun." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/fenbala.npc b/data/npc/fenbala.npc new file mode 100644 index 0000000..1ce230b --- /dev/null +++ b/data/npc/fenbala.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fenbala.npc: Datenbank für die Wachfrau Fenbala + +Name = "Fenbala" +Outfit = (139,77-52-64-115) +Home = [32320,31755,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hail$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"salutations$","queen",! -> "HAIL TO THE QUEEN!" +ADDRESS,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the queen greet with her title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","queen",! -> "Wait for your audience!" +BUSY,"hail$","queen",! -> "Wait for your audience!" +BUSY,"salutations$","queen",! -> "Wait for your audience!" +BUSY,"hi$","queen",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/data/npc/fenech.npc b/data/npc/fenech.npc new file mode 100644 index 0000000..d014255 --- /dev/null +++ b/data/npc/fenech.npc @@ -0,0 +1,67 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# fenech.npc: Datenbank für den pyramidenhändler Fenech + +Name = "Fenech" +Outfit = (132,76-40-49-117) +Home = [33131,32820,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell runes, wands, rods and spellbooks." +"offer" -> * +"name" -> "I am the mourned Fenech." +"time" -> "Buy a watch on the bazar." +"temple" -> "Ask the guards for locations." +"arena" -> * +"palace" -> * + +"arkhothep" -> "Praised may he be." +"ashmunrah" -> "I don't know. Read some books." +"scarab" -> "Scarabs are dangerous. Stay away from them like I do." +"tibia" -> "I know only Ankrahmun. What else could there be?" +"carlin" -> "I don't know any foreign places or races." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> * +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> * +"elves" -> * +"elfes" -> * +"darama" -> "This is our land." +"darashia" -> "Its somewhere in the north as far as I know." +"daraman" -> "I am not a studied person. Ask someone else." +"ankrahmun" -> "Its my home and all I know." + +"ascension" -> "Ask the priest in the temple." +"Akh'rah","Uthun" -> * + +"rune" -> "I sell blank runes and spell runes." +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + + +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Maybe next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/data/npc/ferks.npc b/data/npc/ferks.npc new file mode 100644 index 0000000..8829386 --- /dev/null +++ b/data/npc/ferks.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferks.npc: Datenbank für den Bankier Ferks + +Name = "Ferks" +Outfit = (128,78-52-118-115) +Home = [32634,32738,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "A good day to you." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a banker, my job is to exchange coins." +"name" -> "My name is Ferks ." +"time" -> "It is exactly %T right now." +"king" -> "His royal highness might visit this little but promising community one day if we develop well." +"venore" -> "Our city and society could be a role model for all Tibians." +"thais" -> "Thais is still a power that must be taken into account. Power is not everything of course." +"carlin" -> "Carlin is nothing but a nuisance." +"edron" -> "Edron has little to offer. It's overestimated. Let those silly knights have it. In the end it will be us who clean up the mess and bring order into the chaos that will be left when they finally leave." +"jungle" -> "This jungle is dangerous. The more area we can finally cultivate, the better. Only if we chop and burn enough of it down and create new farmland, we can build a new centre of commerce." + +"tibia" -> "Sometimes I wonder why the gods don't put more effort into bringing us order but I am a banker, not a priest." + +"kazordoon" -> "Dwarves are disciplined people, which I appreciate." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Those elves are erratic and unreliable. They need to be taught some manners." +"elves" -> * +"elfs" -> * +"darama" -> "This wilderness and the dry desert need to be colonised by civilised people like us. We bring the light of order and prosperity to this continent." +"darashia" -> "This town has little that we are interested in." +"ankrahmun" -> "I have to admit that I somehow admire the strong guidance of this pharao. Most fail to acknowledge that his subjects are in need of such a strict leadership." +"ferumbras" -> "A servant of chaos. I wonder why all the Thaian military power can't stop him for good." +"excalibug" -> "A fictitious weapon is of no use at all." +"apes" -> "They mindlessly attack our settlement to steal tools and everything else they get into their hairy hands." +"lizard" -> "They live so far away in the jungle that we know only little of them. Perhaps this primitive race can be used in some way." +"dworcs" -> "Evil little bastards they are." + +@"gen-bank.ndb" +} diff --git a/data/npc/ferryman1.npc b/data/npc/ferryman1.npc new file mode 100644 index 0000000..ab979d1 --- /dev/null +++ b/data/npc/ferryman1.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman1.npc: Fährmann Nielson am Festland (Ice) + +Name = "Nielson" +Outfit = (129,114-113-68-67) +Home = [32232,31677,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Nielson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=20, "Do you want a round-trip passage to Senja for %P gold?", Topic=1 +"folda" -> Price=20, "Do you want a round-trip passage to Folda for %P gold?", Topic=2 +"vega" -> Price=20, "Do you want a round-trip passage to Vega for %P gold?", Topic=3 +"tibia" -> "This is Tibia, the continent." + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * +Topic=3,"yes",PZBlock,! -> * + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/data/npc/ferryman2.npc b/data/npc/ferryman2.npc new file mode 100644 index 0000000..56ce6e1 --- /dev/null +++ b/data/npc/ferryman2.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman2.npc: Fährmann Anderson auf Senja (Ice) + +Name = "Anderson" +Outfit = (129,79-113-68-67) +Home = [32127,31660,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Anderson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> "This island is Senja." +"folda" -> Price=10, "Do you want a passage to Folda for %P gold?", Topic=1 +"vega" -> Price=10, "Do you want a passage to Vega for %P gold?", Topic=2 +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * +Topic=3,"yes",PZBlock,! -> * + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/data/npc/ferryman3.npc b/data/npc/ferryman3.npc new file mode 100644 index 0000000..fc78ab0 --- /dev/null +++ b/data/npc/ferryman3.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman3.npc: Fährmann Svenson auf Folda (Ice) + +Name = "Svenson" +Outfit = (129,77-113-68-67) +Home = [32044,31582,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Svenson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=10, "Do you want a passage to Senja for %P gold?", Topic=1 +"folda" -> "This island is Folda." +"vega" -> Price=10, "Do you want a passage to Vega for %P gold?", Topic=2 +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * +Topic=3,"yes",PZBlock,! -> * + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32025,31692,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/data/npc/ferryman4.npc b/data/npc/ferryman4.npc new file mode 100644 index 0000000..e904fee --- /dev/null +++ b/data/npc/ferryman4.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferryman4.npc: Fährmann Carlson in Vega (Ice) + +Name = "Carlson" +Outfit = (129,19-113-68-67) +Home = [32029,31692,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N and welcome to the Nordic Tibia Ferries." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Carlson from the Nordic Tibia Ferries." +"anderson" -> "The four of us are the captains of the Nordic Tibia Ferries." +"svenson" -> * +"carlson" -> * +"nielson" -> * +"job" -> "We are ferrymen. We transport goods and passengers to the Ice Islands." +"captain" -> * +"ship" -> "Our ferries are strong enough to stand the high waves of the Nordic Ocean." +"ferry" -> * +"ferries" -> * +"water" -> * +"good" -> "We can transport everything you want." +"passenger" -> "We would like to welcome you on board our ferries." +"trip" -> "Where do you want to go today? We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"passage" -> * +"round","trip" -> "The fee for the trip back to Tibia is included." +"island" -> "We serve the routes to Senja, Folda, and Vega, and back to Tibia." +"route" -> * + +"senja" -> Price=10, "Do you want a passage to Senja for %P gold?", Topic=1 +"folda" -> Price=10, "Do you want a passage to Folda for %P gold?", Topic=2 +"vega" -> "This island is Vega." +"tibia" -> Price=0, "Do you want a free passage back to Tibia?", Topic=3 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * +Topic=3,"yes",PZBlock,! -> * + + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32126,31667,7), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32048,31582,7), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." + +Topic=3,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32236,31677,7), EffectOpp(11) +Topic=3,"yes" -> "You don't have enough money." +Topic=3 -> "You shouldn't miss the experience." +} diff --git a/data/npc/ferrymanjack.npc b/data/npc/ferrymanjack.npc new file mode 100644 index 0000000..396f2e2 --- /dev/null +++ b/data/npc/ferrymanjack.npc @@ -0,0 +1,53 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ferrymanJack.npc: Fährmann Jack auf der Mönchsinsel + +Name = "Captain Jack" +Outfit = (132,19-70-95-115) +Home = [32189,31957,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",QuestValue(220)>0,! -> "By the gods! You must be that intruder the good brothers were talking about! Begone!",Idle +BUSY,"hello$",QuestValue(220)>0,! -> * +ADDRESS,"hi$",QuestValue(220)>0,! -> * +BUSY,"hi$",QuestValue(220)>0,! -> * + +ADDRESS,"hello$",! -> "Ahoy, matey. You are lucky to catch me here. I was preparing to set sail." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "I'm m ol' Captain Jack." +"job" -> "I work as a kind of ferryman. I transport wares and travellers for the monks." +"sail" -> * +"captain" -> * +"ship" -> "All right she's small. But she's a real beauty, don't you think?" +"ferry" -> * +"wares" -> "They always need provisions from the cities, and they sell their wine there." +"traveller" -> "Sometimes pilgrims come to this place. And now and then a monk leaves the monastery for some time." +"trip" -> "Where do you want to go today?" +"passage" -> "If you have the abbot's permission, I can take you to the home of a local fisherman on the continent Tibia. We can't sail to Carlin though." +"carlin" -> "For some obscure political reason the monks never sail to Carlin or Thais directly." +"island" -> "This is the isle of the kings. All the great Tibian leaders have found their final rest here under the monastery." +"monastery" -> "The white raven monastery is a place of wisdom and contemplation, or so the monks say. Sounds like a pretty boring place to me! HAR HAR!" +"monk" -> "The order of the white raven." +"white","raven" -> "I prefer parrots. And monkeys! And snakes! HAR! HAR!" +"continent",QuestValue(62)=2 -> Price=20, "Friends of Dalbrect are my friends too! So you are looking for a passage to the continent for %P gold?", Topic=1 +"tibia",QuestValue(62)=2 -> * +"continent",QuestValue(62)=1 -> Price=20, "Do you want a passage to the continent for %P gold?", Topic=1 +"continent",QuestValue(62)<1 -> "Without the abbots permission I won't take sail you anywhere! Go and ask him for a passage first." +"tibia",QuestValue(62)=1 -> Price=20, "Tibia is the main continent. Do you want a passage to the continent for %P gold?", Topic=1 +"tibia",QuestValue(62)<1 -> "Without the abbots permission I won't take you anywhere! Go and ask him for a passage first." + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32205,31756,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Well, I'll be here if you change your mind." + +} diff --git a/data/npc/frans.npc b/data/npc/frans.npc new file mode 100644 index 0000000..4147dfc --- /dev/null +++ b/data/npc/frans.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# frans.npc: Datenbank für den Magieladen-Verkäufer Frans + +Name = "Frans" +Outfit = (0,3114) +Home = [32971,32080,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Beeee Greeeeted %N. What is your neeeed?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, I am veeeerry busy. Unfoooortunately you will have to waiiiit.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"name" -> "I am a FRANS." +"frans" -> "Floating ReeeeAnimated Necromantic Seeeervant ... FRANS." +"job" -> "I am selliiiing ruuuunes, wands, roooods and spellbooooooks." +"sorcerer" -> "Sorcerorssss, druidssss, they all come to ussss." +"druid" -> * +"magic" -> "Is aaaall about magic more or lesssss, isn't it?" +"vladruc" -> "Heeee is the bossss. Better don't messss with him!" +"urghain" -> * +"ferumbras" -> "Wouldn't he beeee the perfect FRANS?" +"market" -> "Yes, that's a market heeeere, smarty ... Nice to seeeee I am not the only one without a braiiiin here." +"excalibug" -> "We FRANSes don't liiiike any bugssss." + + +"offer" -> "What do youuuu think I am? A lousy barberrrr? I'm selliiiing ruuuunes and spellboooooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank ruuuuunes and spell ruuuuunes." +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do youuuu want to buy a blank ruuuune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do youuuu want to buy a spellboooook for %P gold?", Topic=1 + + +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do youuuu want to buy %A blank ruuuunes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do youuuu want to buy %A spellboooooks for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here it isssss! Almost like magiiiic, isn't it?", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, too many buttonssss and not enough gold in your pursssse." +Topic=1 -> "Better not annoy a FRANS if you don't want to deeeeal with him!" + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/data/npc/frodo.npc b/data/npc/frodo.npc new file mode 100644 index 0000000..52545f5 --- /dev/null +++ b/data/npc/frodo.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# frodo.npc: Datenbank fuer den Wirt Frodo + +Name = "Frodo" +Outfit = (128,58-68-109-131) +Home = [32356,32209,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","frodo",! -> "Hello, hello, %N. You heard about the news?" +ADDRESS,"hi$","frodo",! -> * +ADDRESS,"hello$",! -> "Welcome to Frodo's Hut. You heard about the news?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N. I'll be with you in no time.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back from time to time." + +"bye" -> "Please come back from time to time.", Idle +"farewell" -> * +"job" -> "I am the owner of this saloon. I call it Frodo's Hut. I am also selling food." +"saloon" -> * +"hut" -> "I hope you like it." +"name" -> "Just call me Frodo." +"time" -> "It is exactly %T." +"king" -> "Oh, our beloved king! Thanks to him, alcohol is so cheap." +"tibianus" -> * +"quentin" -> "He hardly visits my humble tavern." +"lynda",female -> "A very noble lady." +"lynda",male -> "Just between you and me: What a babe!" +"harkath" -> "Too disciplined to enjoy life." +"army" -> "Hehe. Great customers." +"ferumbras" -> "Uhm, do not mention him. It may scare customers away." +"general" -> "Harkath Bloodblade is the royal general." +"sam" -> "A loud neighbour, I get a lot of complaints about him." +"xodet" -> "I don't know where he gets these fluids. If I could sell them here, the hut would be crowded." +"gorn" -> "Many of his customers visit my Hut, too." +"elane" -> "Can you believe that she actually told her guildfellows that alcohol is a bad thing?" +"muriel" -> "Muriel has never visited this place." +"lungelen" -> "A sorceress, you can find her in their guild sitting befor a book - always!" +"gregor" -> "The knights have sometimes parties here after some arena fights." +"marvik" -> "Marvik seldom leaves his guildhall at all." +"bozo" -> "I am trying to hire him for an evening or two." +"baxter" -> "He's able to drink a bottle or two." +"oswald" -> "I hate him. Each of his visits here ends with a bar brawl." +"sherry" -> "The McRonalds are a nice couple. Donald is a dear friend of mine." +"mcronald" -> * +"donald" -> "He is a little shy. In his youth he dreamed to become a druid." +"lugri" -> "I overheared some conversations about his evilness. That's enough to hope, that I never ever meet him." +"excalibug" -> "Nothing more than a tale for warriors." +"thais" -> "Here in Thais is the center of Tibia." +"tibia" -> "Come on! You know that our world is called Tibia." +"carlin" -> "Many travellers tell funny stories about all the emancipated women in this northern town." +"rain","castle" -> "The king's residence has been renovated lately." +"galuna" -> "She makes excellent arrows and bows." +"hugo" -> "I think some time ago a stranger from Fibula with that name stayed here for some nights." +"todd" -> "That fellow is filthy rich. He rented a room upstairs for months in advance and always orders the best beer and wine i serve." + +"news" -> "Some travelers from Edron told about a great treasure guarded by cruel demons in the dungeons there." +"rumors" -> * + +"buy" -> "I can offer you bread, cheese, ham, or meat." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"satanic" -> "Hmm, I have heard of a 'satanic influence' theory by someone called Newton or something like that... Maybe there's more in the Royal Archives." +"cropwell" -> "No idea who that is, but maybe you'll find something in the Royal Archives..." +"alistair" -> * +"archives" -> "Oh, the Royal Archives are in Rain Castle!" +"dungeon" -> "Ah yes, the graveyard dungeon. All I know is this riddle: His Grave to the south, the tree above, his soul in the shade. No idea what that means, though!" +"graveyard" -> * +"riddle" -> "I heard it when I was a child." +"sunset","homes" -> "The sunset homes are a block of flats south of the harbour." +"one","eyed","stranger" -> "Yes, I remember him. His name was Berfasmur." +"berfasmur" -> "Sorry, he spoke only very little. I know nothing more about him." + +"hengis","wulfson" -> "He is a great bard. He often graced my hut with his presence, songs, and rhymes. I wonder what happened to him lately.", Topic=2 +Topic=2,"died" -> "Oh, by the gods! What do you say happened to him?", Topic=3 +Topic=2,"killed" -> * +Topic=2,"dead" -> * +Topic=2,"death" -> * +Topic=2,"slain" -> * +Topic=3,"killed","cyclops" -> "That's horrible! I am in grief. I will never hear his songs again. I will even miss that strange rhyme he was obsessed with.", Topic=4 +Topic=3,"slain","cyclops" -> * +Topic=3,"cyclops","slay" -> * +Topic=3,"cyclops","kill" -> * +Topic=4,"rhyme" -> "He recitated it that often that I learned it by heart myself. I would recitate it, but I am not skilled in that kind of things.", Topic=5 +Topic=5,"recitate" -> "Uhm. If you insist, but I am so awful. I will stop now and then and wait, so you can tell if I should proceed, ok?", Topic=6 +Topic=6,"yes" -> "Well ok, but don't blame me. Chhrrr... chhrrrr,... it goes like this... chhrrr: and when the dead feast at midnight...", Topic=7 +Topic=7,"proceed" -> "... the ancient enemy will no longer guard the place of his unlucky heir and the living will walk the paths of the old way...", Topic=8 +Topic=8,"proceed" -> "... Death awaits the greedy and the brave alike and many will be mourned until the long lost treasure is unearthed.", Topic=9 +Topic=9,"proceed" -> "That's all. He recitated it when he was in one of his melancholy moods." +} diff --git a/data/npc/gabel.npc b/data/npc/gabel.npc new file mode 100644 index 0000000..21872ab --- /dev/null +++ b/data/npc/gabel.npc @@ -0,0 +1,124 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gabel.npc: Datenbank für den Djinnkönig Gabel + +Name = "Gabel" +Outfit = (80,0-0-0-0) +Home = [33102,32520,1] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Welcome, human %N, to our humble abode." +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Have patience, %N. In this world every grain of sand has its place and its time. You may have found the place you have been looking for, but it is not yet your time.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, stranger. May Uman open your minds and your hearts to Daraman's wisdom!" + +"bye" -> "Farewell, stranger. May Uman open your minds and your hearts to Daraman's wisdom!", Idle +"farewell" -> * +"name" -> "I am known as Gabel. I have borne this name for as long as I remember, and believe me - that is quite some time." +"gabel" -> "I have often thought about changing this name because... Ah well . Let us talk about more cheerful things." +"job" -> "I am the true leader of all djinn, both in worldly and in spiritual matters. Unfortunately, there are those among my kind who disagree." +"djinn" -> "Once we were a mighty race. I like to think that one day we will return to our former glory, but as long as this tragic war is not won that is not likely to happen." +"war" -> "We had thought the war was over for good when Malor was finally imprisoned. And now he is free again! How could it ever come so far. ...", + "But well - nothing is lost. I have no illusions about Malor's intentions, but I'm not afraid. We have beaten him once and we can beat him again!" +"malor" -> "The accursed usurper is free! I can't believe it! To think that power-hungry cockroach is once again roaming the world! ...", + "I could forgive him his evil schemes if he had not led my people into this tragic fratricidal war!" +"king" -> "Some call me a king, even though I do not like the title. Daraman has taught us to think little of worldly matters such as power or station in life." +"leader" -> * +"daraman" -> "Daraman may have been just a human, but he bore in him the spark of the divine. We have paid a heavy price for following his teachings, but I have never felt any regret for my decision." +"ashta'daramai" -> "This place is a gift of Daraman to the djinn people. Oh, he did not build it himself, of course. It was us Marid who did it. We erected it on the place where once stood my old palace. ...", + "Its serene majesty is a visual expression of the inner peace and light that are bestowed by the great Daraman's teachings." +"abode" -> * +"marid" -> "We, the Marid, are the true inheritors of the djinn legacy. Those errant fools who call themselves the Efreet are nothing but usurpers." +"efreet" -> "Our fallen brethren claim they are different, but I have not given up hope yet that all djinn will be reunited one day! If only they saw the light!" + +"mal'ouquah" -> "The Mal'ouquah is the Efreets' fortress. Malor built it when it was clear that the djinns had definitely split in two fractions. I will personally raze it to the ground once we have finally won this disastrous war!" +"kha'zeel" -> "These majestic mountains were chosen by the gods as a vantage point from which they watched their creation." +"kha'labal" -> "Ah yes - that horrid desert. I still recall how beautiful it was back in the times before the struggle it began. It was a land full of song and bliss - a veritable paradise. But look at it now. It is such a shame." +"orc","king" -> "The power hungry fool released Malor from his prison, and now the evil is upon us once again! He should have known better than to believe Malor's sugar covered lies. ...", + "But what can you expect from a power-crazed, stupid-as-a-brick orc. Nothing but blockheads the lot of them" +"human" -> "For a long time we have despised and oppressed your kind. I still feel ashamed for the things we have done in those dark days. The gods be praised that they sent Daraman to open our eyes. ...", + "I know that one day djinn and humans will live in peaceful co-existence." +"zathroth" -> "The name brings up painful memories. I'd rather not talk about this subject." +"gods" -> "For a long time I found it difficult to love them. But Daraman has opened my eyes." +"tibia" -> "The world of Tibia is like a gemstone carved by the greatest of all craftsmen. It is sad that it took a human to make me realise its perfection." + +"darashia" -> "Darashia was nothing but a forlorn pool of mud last time I passed there. I hear it has now risen to great wealth and glory. Perhaps the Caliph is more open to the true creed than that dangerous fool, the pharaoh of Ankrahmun." +"scarab" -> "The scarabs are ancient beings, as ancient as ourselves. We djinns feel a lot of respect for them." +"edron" -> "I have often heard of the splendid cities the humans have erected on the continent. I would like to visit them one day and send them a message of peace and friendship in the name of my people." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I haven't seen the venerable city in a long time. I would like to visit it again, but I can't help the feeling that I would not be welcome these days." + +"pharaoh" -> "The new pharaoh seems to hold some very eccentric ideas about life and death. I have a feeling that his so-called teachings are nothing but an ignorant perversion of the true creed." +"palace" -> "Stay clear of that place. I have heard bad things about it." +"temple" -> "In these heretic times the temple is devoted to the teachings of that pompous pharaoh. I haven't been there for a long time." +"ascension" -> "Apparently that is what the followers of the pharaoh are striving for. It has to do with his heretical teachings." +"rah" -> "That's just some heretic drivel. Don't ask me about it." +"uthun" -> * +"akh" -> * + +"djema" -> "Poor thing. She's an orphan you know. We took her to us when she was a child, and we have never regretted it. So fresh and lively - she has really brought some life to this place. ...", + "In a way it was an experiment, you know. A human living among djinn. I suppose it worked well, but perhaps too well. ...", + "Now she has grown on me, and I'm loath to let her go. Sometimes I wonder if it was right to make her live in this place after all." +"bo'ques" -> "My good old cook. Of all worldly temptations his food is the greatest." +"baa'leal" -> "He is Malor's lieutenant and the commander-in-chief of all his minions. He is nothing without his master. Never stirred while Malor was gone." +"alesar" -> "Alesar... Alesar. You know, it pains me to even hear this name. When he left I lost both my best smith and a personal friend. I don't know what is worse. ...", + "And the worst is, to this day I have never managed to figure out why he left. I refuse to believe that Malor could bribe him in any way. If only I knew." +"fa'hradin" -> "He is my trusted counsellor and friend. If you would like to help us you should talk to him. ...", + "By the way - don't worry if his behaviour appears a bit odd sometimes. He is incurably eccentric. Always has been. I think it is a job hazard of being a wizard." +"lamp" -> "We djinn use them to sleep. Well, you may find it is a funny notion to sleep in a lamp, but then, for us it seems just as silly to sleep in a longish wooden construction with a fluffy mattress on top." +"fa'hradin","lamp" -> "Ah yes. This lamp was his masterpiece. It was so satisfying to see that dirty little schemer fall for a ploy himself. If only he'd never come back!" +"rata'mari" -> "So you know about him. Hm. Since nobody else knows about him Fa'Hradin must have told you. ...", + "I suppose he had his reasons, but I would appreciate it if you did not tell anybody about him. If Malor found about him, he would start a little rat hunt I guess." + +"permission",QuestValue(283)<3 -> "I am not yet convinced, that we can trust you, %N. Only trusted people are allowed to trade with Haroun and Nah'Bob." +"permission",QuestValue(283)=3 -> "You are welcome to trade with Haroun and Nah'bob whenever you want to, %N!" + +"work",QuestValue(281)<2 -> "So you would like to fight for us, don't you. Hmm. ...", + "That is a noble resolution you have made there, human, but I'm afraid I cannot accept your generous offer at this point of time. ...", + "Do not get me wrong, but I am not the kind of guy to send an inexperienced soldier into certain death! So you might ask around here for a more suitable mission." +"mission",QuestValue(281)<2 -> * + +"report",QuestValue(281)=2,QuestValue(283)=0 -> "Sooo. Fa'hradin has told me about your extraordinary exploit, and I must say I am impressed. ...", + "Your fragile human form belies your courage and your fighting spirit. ...", + "I hardly dare to ask you because you have already done so much for us, but there is a task to be done, and I cannot think of anybody else who would be better suited to fulfill it than you. ...", + "Think carefully, human, for this mission will bring you into real danger. Are you prepared to do us that final favour?", Topic=1 +"spyreport",QuestValue(281)=2,QuestValue(283)=0 -> * +"work",QuestValue(281)=2,QuestValue(283)=0 -> * +"mission",QuestValue(281)=2,QuestValue(283)=0 -> * + +"report",QuestValue(283)=1 -> "You haven't finished your final mission yet. Shall I explain it again to you?", Topic=1 +"spyreport",QuestValue(283)=1 -> * +"work",QuestValue(283)=1 -> * +"mission",QuestValue(283)=1 -> * +"lamp",QuestValue(283)=1 -> * + +Topic=1,"yes" -> "All right. Listen! Thanks to Rata'mari's report we now know what Malor is up to: he wants to do to me what I have done to him - he wants to imprison me in Fa'hradin's lamp! ...", + "Of course, that won't happen. Now, we know his plans. ...", + "But I am aiming at something different. We have learnt one important thing: At this point of time, Malor does not have the lamp yet, which means it is still where he left it. We need that lamp! If we get it back we can imprison him again! ...", + "From all we know the lamp is still in the Orc King's possession! Therefore I want to ask you to enter the well guarded halls over at Ulderek's Rock and find the lamp. ...", + "Once you have acquired the lamp you must enter Mal'ouquah again. Sneak into Malor's personal chambers and exchange his sleeping lamp with Fa'hradin's lamp! ...", + "If you succeed, the war could be over one night later! I and all djinn will be in your debt forever! May Daraman watch over you!", SetQuestValue(283,1) +Topic=1 -> "As you wish." + +"report",QuestValue(283)=2 -> "Have you found Fa'hradin's lamp and placed it in Malor's personal chambers? ", Topic=2 +"spyreport",QuestValue(283)=2 -> * +"work",QuestValue(283)=2 -> * +"mission",QuestValue(283)=2 -> * +"lamp",QuestValue(283)=2 -> * + +Topic=2,"yes" -> "Daraman shall bless you and all humans! You have done us all a huge service! Soon, this awful war will be over! ...", + "Know, that from now on you are considered one of us and are welcome to trade with Haroun and Nah'bob whenever you want to!", SetQuestValue(283,3) +Topic=2 -> "Don't give up! May Daraman watch over you!" +} diff --git a/data/npc/gail.npc b/data/npc/gail.npc new file mode 100644 index 0000000..9235cc1 --- /dev/null +++ b/data/npc/gail.npc @@ -0,0 +1,106 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gail.npc: Datenbank für die edelsteinhändlerin gail + +Name = "Gail" +Outfit = (140,77-64-52-95) +Home = [32621,32738,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Please feel welcome." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment. I'll be with you within a heartbeat.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am buying and selling gems and jewellery." +"name" -> "My name is Gail, nice to meet you." +"time" -> "I am sorry but watches are not that common here. Time has little meaning in this quite boring colony." +"king" -> "The Thaian monarch entrusted Venore with much responsibility to manage this colony. Sadly he did not entrust the entire control in our hands which is a constant reason for all kinds of problems." +"venore" -> "Venore is a beautiful city and its community is built up on commerce. Therefore it serves the needs of everyone who wants to contribute his share to the welfare of the city." +"thais" -> "A somewhat chaotic town. I understand how its growth dictated the shape of its community but I also see the flaws in the outcome." +"carlin" -> "Their independence is just insane viewed from an economic angle. A complete waste of resources." +"edron" -> "Another area that could and should prosper under Venoran guidance. If the king sees how we handle this settlement, he will for sure be impressed enough to allow us more freedom on that promising isle." +"jungle" -> "As soon as we have handled the problem with the natives, wealth is awaiting us, considering the endless resources waiting there to be taken." + +"tibia" -> "There is so much left that still needs to be discovered in this world." + +"kazordoon" -> "Dwarven craftsmen are some of the best in their fields. Sadly they charge quite a lot for their work and it's hard to make some profit with dwarven wares." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Elves are bad customers. We did not manage to break into their market yet." +"elves" -> * +"elfs" -> * +"darama" -> "This continent is a challenge and an opportunity at the same time." +"darashia" -> "People there are somewhat strange. In my opinion their philosophy isn't good for business but the minds of people can be changed if you use the right arguments." +"ankrahmun" -> "Well, I admit I fail to see any profit that could safely be made by trading with that city, but even unsafe profit is a good one. Prices have to be adjusted accordingly to the course." +"ferumbras" -> "One evil sorcerer, no matter how powerful he might be, can have only a certain influence on the market. There are other threats to our profit that are more urgent." +"excalibug" -> "A knight's fairy tale." +"apes" -> "Those animals are one of the worst things we have to face here." +"lizard" -> "There must be some way to leverage their hatred towards those apes." +"dworcs" -> "Luckily, this horrible ugly things stay in their own territory but who knows for how long? We should never show them any sign of weakness." + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"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." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +} diff --git a/data/npc/galuna.npc b/data/npc/galuna.npc new file mode 100644 index 0000000..721030b --- /dev/null +++ b/data/npc/galuna.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# galuna.npc: Datenbank für die Bogenmacherin Galuna + +Name = "Galuna" +Outfit = (137,40-96-95-96) +Home = [32342,32246,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the local fletcher. I am selling bows, crossbows, and ammunition. Do you need anything?" +"fletcher" -> * +"name" -> "I am Galuna, paladin and fletcher." +"paladin" -> "We are feared warriors and good marksmen. Ask Elane if want to know more about the guild." +"elane" -> "She is the leader of all paladins." +"gorn" -> "I supplied him with my goods in the past, now I sell them myself." +"time" -> "Don't bother me. Go and buy a watch." +"tibia" -> "Tibia, a green island. Here it is wunderful to walk into the forests and to hunt with a bow." +"forest" -> * +"thais" -> "We have visitors of all kind in Thais, only elves show up seldom." +"elf" -> "It is rumored that they live in the northeast of Tibia. They are the best in archery." +"elves" -> * + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/gamel.npc b/data/npc/gamel.npc new file mode 100644 index 0000000..8cf8f19 --- /dev/null +++ b/data/npc/gamel.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Gamel.npc: Datenbank für den Gauner Gamel + +Name = "Gamel" +Outfit = (129,79-132-115-116) +Home = [32337,32207,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Pssst! Be silent. Do you wish to buy something?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not right now." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye. Tell others about... my little shop here." + +"bye" -> "Bye. Tell others about... my little shop here.", Idle +"farewell" -> * +"job" -> "I am selling some... things." +"name" -> "Names don't matter." +"gamel" -> "Oh, you know my name. Please don't tell it to the others." + +"rebellion" -> "Uhm... who sent you?", Topic=3 +"berfasmur" -> "Never heard that name!" + +"offer" -> "I sell maces, staffs, daggers and brass helmets." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"things" -> * +"help" -> * + +"staff" -> Type=3289, Amount=1, Price=40, "Do you want to buy it for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy it for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy it for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy it for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy it for %P gold?", Topic=1 + +%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=40*%1, "Do you want to buy %A staffs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "Do you want to buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Hey, you do not have enough gold." +Topic=1 -> "Maybe we will make the deal another time." + +"magic","crystal" -> Type=3061, Amount=1, "Did you bring me a magic crystal?", Topic=4 +Topic=3,"berfasmur" -> "So, you are a new recruit in the ranks of the rebellion! To proof your worthyness, go and get us a magic crystal." +Topic=4,"yes",Count(Type)>=Amount -> "Brilliant! Bring it to the priest Lugri so that he can cast a deathcurse on the king. The password is 'death to noodles'." +Topic=4,"yes",Count(Type) "Idiot! You don't have the crystal!", Poison(2,10), EffectOpp(9), EffectMe(15) +} diff --git a/data/npc/gamon.npc b/data/npc/gamon.npc new file mode 100644 index 0000000..9f69cc1 --- /dev/null +++ b/data/npc/gamon.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gamon.npc: Möbelverkäufer Gamon in Thais + +Name = "Gamon" +Outfit = (128,97-58-105-120) +Home = [32408,32170,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",female,! -> "Well, hello there, Lady, %N welcome to Gamon's humble furniture shop!" +ADDRESS,"hi$",female,! -> * +ADDRESS,"hello$",male,! -> "Nice to meet you, Mister %N! Looking for furniture? You've come to the right place!" +ADDRESS,"hi$",male,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, please. I got a customer here." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,male,! -> "Now where's he gone? I could have sworn..." +VANISH,! -> "Lady? Lady?? Hm. How strange..." + +"bye" -> "You'll come back. They all do.", Idle +"farewell" -> * +"job" -> "I am Thais's foremost furniture salesman." +"news" -> "News? Of course there's news! There's a new furniture man in town, and he's here to stay!" +"how","are","you"-> "Excellent! Never felt better in my life!" +"name" -> "My friends call me Gamon. My fans call me the incredible Gammy!" +"time" -> "Any time's a good time to buy some furniture." +"Thais" -> "Thais is obsessed with its past. Everybody here is so proud of their history. Bah! Thais might have a long history, but it has no idea when it comes to interior decoration." +"Venore" -> "The place where it all happens. That town really rocks! Thais could learn a lot about interior decoration from Venore!" +"power" -> "There are a few rumours about a rebellion, but that is all they are." +"rebellion" -> "Well - a few paranoid souls think that Venore wants to gain independence from Thais. Nothing more than rumours." + +"news" -> "You mean my specials, don't you?" + +"quality" -> "Our furniture is produced by the finest carpenters on the continent using the rare wood of the Venorean marsh willow!" +"marsh","willow" -> "You can't get any better wood in this world. And it has got a nice smell to it, too. There is nothing nicer than a marsh willow campfire." +"king" -> "His Royal Highness will start to appreciate the superior quality of our stock soon enough!" +"sam" -> "I heard rumours he has some special offers for customers who know to ask for the correct things." +"benjamin" -> "He's incredibly slow. Just your average postman, I guess." +"gorn" -> "He sells stuff of inferior quality. Nothing compared to venores high quality goods." +"quentin" -> "That old monk has probably never left that overcrowded town here." +"quest" -> "A quest?! Who needs quests when there is interior decorating!" +"bozo" -> "Bozo! Damn that clown! He keeps making fake orders. It isn't funny to deliver a wardrobe to an address that doesn't even exist, you know!" +"tibia" -> "Tibia is a wonderful place full of business opportunities." +"castle" -> "I've said it a thousand times! That place needs a complete refurbishing!" +"muriel" -> "The sorcerers guild could realy need someone with taste to redecorate it." +"elane" -> "She's pretty, but I am the kind of man who enjoys a long and healthy life." +"marvik" -> "Druids are obsessed with trees and the bane of any carpenter." +"gregor" -> "Those knights know how to party. And after such partys theres allways need for new furniture." +"guild" -> "Now those people really talk business. There is a new era dawning." +"merchants" -> * +"Topsy" -> "Ah, those twins. Strange people they are (*sigh*). Oh, they are great to work with, of course. Excellent quality, competetive prices! But well... (whispers) they give me the creeps!" +"Turvy" -> * +"twins" -> * +"creeps" -> "Yes! I can never figure out which is which. And they are always watching me!" +"watching" -> "Look! They are doing it again! And they are always smiling!" +"smiling" -> "Yes! Smiling! How a professional sales artist such as myself is supposed to work in such an atmosphere is beyond me!" +"artist" -> "Yes! Selling is a form of art! The elaborate combination of rhetoric and acting which serves to create a sublime longing for the infinite, embodied by second class furniture." +"rug" -> "Oh, silly me! Rugs are out of stock at the moment! But we expect a new shipment anytime. Just watch out for the next update!... Of our inventory I mean." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/gatekeeper.npc b/data/npc/gatekeeper.npc new file mode 100644 index 0000000..ccf738d --- /dev/null +++ b/data/npc/gatekeeper.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gatekeeper.npc: Datenbank fuer das Premium Orakel auf Rookgaard + +Name = "The Gatekeeper" +Outfit = (0,2031) +Home = [32035,32183,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",Level>=8,premium,! -> "%N, ARE YOU PREPARED TO FACE YOUR DESTINY?" +ADDRESS,"hi$",Level>=8,premium,! -> * +ADDRESS,"greet",Level>=8,premium,! -> * + +ADDRESS,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"greet",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Level>=8,premium,! -> "WAIT UNTIL IT IS YOUR TURN!", Queue +BUSY,"hi$",Level>=8,premium,! -> * +BUSY,"greet",Level>=8,premium,! -> * +BUSY,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!" +BUSY,"hi$",! -> * +BUSY,"greet",! -> * +BUSY,! -> NOP +VANISH,! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!" + +"yes",premium -> "IN WHICH TOWN DO YOU WANT TO LIVE: AB'DENDRIEL, KAZORDOON, ANKRAHMUN, PORT HOPE OR DARASHIA?", Topic=1 +"yes" -> "YOU ARE NOT WORTHY!", Idle +"bye",! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!", Idle + -> * + +Topic=1,"Ab'Dendriel" -> Data=1, "IN AB'DENDRIEL! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"kazordoon" -> Data=2, "IN KAZORDOON! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"darashia" -> Data=3, "IN DARASHIA! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"ankrahmun" -> Data=4, "IN ANKRAHMUN! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"port","hope" -> Data=5, "IN PORT HOPE! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 + + +Topic=1,premium -> "AB'DENDRIEL, KAZORDOON, ANKRAHMUN, PORT HOPE OR DARASHIA?", Topic=1 + + +Topic=2,"knight" -> Type=4, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"paladin" -> Type=3, "A PALADIN! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"sorcerer" -> Type=1, "A SORCERER! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"druid" -> Type=2, "A DRUID! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2 -> "KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 + +Topic=3,Data=1,"yes" -> "SO BE IT!", Profession(Type), Town(4), Idle, EffectOpp(11), Teleport(32732,31634,7), EffectOpp(11) +Topic=3,Data=2,"yes" -> "SO BE IT!", Profession(Type), Town(3), Idle, EffectOpp(11), Teleport(32649,31925,11), EffectOpp(11) +Topic=3,Data=3,"yes" -> "SO BE IT!", Profession(Type), Town(6), Idle, EffectOpp(11), Teleport(33213,32454,1), EffectOpp(11) +Topic=3,Data=4,"yes" -> "SO BE IT!", Profession(Type), Town(8), Idle, EffectOpp(11), Teleport(33194,32853,8), EffectOpp(11) +Topic=3,Data=5,"yes" -> "SO BE IT!", Profession(Type), Town(9), Idle, EffectOpp(11), Teleport(32595,32744,6), EffectOpp(11) + + +} diff --git a/data/npc/gen-bank.ndb b/data/npc/gen-bank.ndb new file mode 100644 index 0000000..2c5ebb6 --- /dev/null +++ b/data/npc/gen-bank.ndb @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-post.ndb: Datenbank für generischen Banker +# Verwendete Topics: 91 bis 99 +# Topic 91(96): Gold -> Platinum +# Topic 92(97): Platinum <- Crystal +# Topic 93(98): Gold <- Platinum +# Topic 94(99): Platinum -> Crystal + +"bank" -> "We can change money for you." +"offer" -> "We exchange gold, platinum and crystal coins." +"sell" -> * +"do","you","have" -> * +"buy" -> * +"money" -> * +"change" -> * +"exchange" -> * + +"change","gold",! -> "How many platinum coins do you want to get?", Topic=91 +"exchange","gold",! -> * +"change","platinum",! -> "Do you want to change your platinum coins to gold or crystal?", Topic=95 +"exchange","platinum",! -> * +"change","crystal",! -> "How many crystal coins do you want to change to platinum?", Topic=92 +"exchange","crystal",! -> * +"change" -> "Do you want to exchange gold, platinum or crystal coins?" + +Topic=95,"gold",! -> "How many platinum coins do you want to change to gold?", Topic=93 +Topic=95,"crystal",! -> "How many crystal coins do you want to get?", Topic=94 +Topic=95 -> "Well, can I help you with something else?" + +Topic=91,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %A of your gold coins to %P platinum coins for you?", Topic=96 +Topic=91 -> "Hmm, can I help you with something else?" + +Topic=92,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %A of your crystal coins to %P platinum coins for you?", Topic=97 +Topic=92 -> "Well, can I help you with something else?" + +Topic=93,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %A of your platinum coins to %P gold coins for you?", Topic=98 +Topic=93 -> "Well, can I help you with something else?" + +Topic=94,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %A of your platinum coins to %P crystal coins for you?", Topic=99 +Topic=94 -> "Well, can I help you with something else?" + +Topic=96,"yes",Count(3031)>=Amount -> "Here you are.", Delete(3031), Amount=Price, Create(3035) +Topic=96,"yes" -> "Sorry, you don't have enough gold coins." +Topic=96 -> "Well, can I help you with something else?" + +Topic=97,"yes",Count(3043)>=Amount -> "Here you are.", Delete(3043), Amount=Price, Create(3035) +Topic=97,"yes" -> "Sorry, you don't have so many crystal coins." +Topic=97 -> "Well, can I help you with something else?" + +Topic=98,"yes",Count(3035)>=Amount -> "Here you are.", Delete(3035), Amount=Price, Create(3031) +Topic=98,"yes" -> "Sorry, you don't have so many platinum coins." +Topic=98 -> "Well, can I help you with something else?" + +Topic=99,"yes",Count(3035)>=Amount -> "Here you are.", Delete(3035), Amount=Price, Create(3043) +Topic=99,"yes" -> "Sorry, you don't have so many platinum coins." +Topic=99 -> "Well, can I help you with something else?" + +#Topic=91,%1,0<%1 -> Amount=%1, Price=100*%1, "So I should change %P gold coins to %A platinum coins for you?", Topic=96 +#Topic=91 -> "Hmm, can I help you with something else?" + +#Topic=92,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %P crystal coins to %A platinum coins for you?", Topic=97 +#Topic=92 -> "Well, can I help you with something else?" + +#Topic=93,%1,0<%1 -> Amount=%1*100, Price=%1, "So I should change %P platinum coins to %A gold coins for you?", Topic=98 +#Topic=93 -> "Well, can I help you with something else?" + +#Topic=94,%1,0<%1 -> Amount=%1, Price=%1*100, "So I should change %P platinum coins to %A crystal coins for you?", Topic=99 +#Topic=94 -> "Well, can I help you with something else?" + +#Topic=96,"yes",Count(3031)>=Price -> "Here you are.", Create(3035), Amount=Price, Delete(3031) +#Topic=96,"yes" -> "Sorry, you don't have enough gold coins." +#Topic=96 -> "Well, can I help you with something else?" + +#Topic=97,"yes",Count(3043)>=Price -> "Here you are.", Create(3035), Amount=Price, Delete(3043) +#Topic=97,"yes" -> "Sorry, you don't have so many crystal coins." +#Topic=97 -> "Well, can I help you with something else?" + +#Topic=98,"yes",Count(3035)>=Price -> "Here you are.", Create(3031), Amount=Price, Delete(3035) +#Topic=98,"yes" -> "Sorry, you don't have so many platinum coins." +#Topic=98 -> "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?" diff --git a/data/npc/gen-post.ndb b/data/npc/gen-post.ndb new file mode 100644 index 0000000..75a0d09 --- /dev/null +++ b/data/npc/gen-post.ndb @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-post.ndb: Datenbank für generischen Postler + +"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=35 +"depot" -> "The depots are very easy to use. Just step in front of them and you will find your items in them. They are free for all tibian citizens." +"offer" -> "I'm selling letters and parcels." + +"letter",QuestValue(250)>0 -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=36 +"parcel",QuestValue(250)>0 -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=37 + +"letter" -> Amount=1, Price=8, "Do you want to buy a letter for %P gold?", Topic=36 +"parcel" -> Amount=1, Price=15, "Do you want to buy a parcel for %P gold?", Topic=37 + +%1,1<%1,"letter",QuestValue(250)>0 -> Amount=%1, Price=5*%1, "Do you want to buy %A letters for %P gold?", Topic=36 + +%1,1<%1,100<%1,"parcel",QuestValue(250)>0 -> Amount=100, Price=10*100, "Do you want to buy %A parcels for %P gold?", Topic=37 +%1,1<%1,"parcel",QuestValue(250)>0 -> Amount=%1, Price=10*%1, "Do you want to buy %A parcels for %P gold?", Topic=37 + +%1,1<%1,"letter" -> Amount=%1, Price=8*%1, "Do you want to buy %A letters for %P gold?", Topic=36 + +%1,1<%1,100<%1,"parcel" -> Amount=100, Price=15*100, "Do you want to buy %A parcels for %P gold?", Topic=37 +%1,1<%1,"parcel" -> Amount=%1, Price=15*%1, "Do you want to buy %A parcels for %P gold?", Topic=37 + + +Topic=35,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +Topic=35 -> "Is there anything else I can do for you?" + +Topic=36,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +Topic=36,"yes" -> "Oh, you have not enough gold to buy a letter." +Topic=36 -> "Ok." + +Topic=37,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +Topic=37,"yes" -> "I am sorry, you have not enough gold to buy a parcel." +Topic=37 -> "Ok." diff --git a/data/npc/gen-t-armor-b.ndb b/data/npc/gen-t-armor-b.ndb new file mode 100644 index 0000000..d25bfa3 --- /dev/null +++ b/data/npc/gen-t-armor-b.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-armor-b.ndb: Datenbank für generischen Rüstungskauf + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell this coat for %P gold?", Topic=51 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell this jacket for %P gold?", Topic=51 +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=51 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=51 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=51 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=51 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell this knights armor for %P gold?", Topic=51 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500, "Do you want to sell this golden armor for %P gold?", Topic=51 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell this coats for %P gold?", Topic=51 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell this jackets for %P gold?", Topic=51 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell a leather armors for %P gold?", Topic=51 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell a chain armors for %P gold?", Topic=51 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell a brass armors for %P gold?", Topic=51 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell a plate armors for %P gold?", Topic=51 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell this knights armors for %P gold?", Topic=51 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1, "Do you want to sell this golden armors for %P gold?", Topic=51 + + +Topic=51,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=51,"yes" -> "Sorry, you do not have one." +Topic=51,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=51 -> "Maybe next time." + diff --git a/data/npc/gen-t-armor-s.ndb b/data/npc/gen-t-armor-s.ndb new file mode 100644 index 0000000..d8140cf --- /dev/null +++ b/data/npc/gen-t-armor-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTarmorV.ndb: Datenbank für generischen Rüstungsverkauf + +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=21 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=21 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a dublet for %P gold?", Topic=21 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=21 +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=21 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=21 +"plate","armor" -> Type=3357, Amount=1, Price=1200,"Do you want to buy a plate armor for %P gold?", Topic=21 + +Topic=21,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=21,"yes" -> "Sorry, you do not have enough gold." +Topic=21 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-distance-s.ndb b/data/npc/gen-t-distance-s.ndb new file mode 100644 index 0000000..baf94d9 --- /dev/null +++ b/data/npc/gen-t-distance-s.ndb @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-distance-s.ndb: Datenbank für generischen Fernwaffenverkauf + + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=34 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=34 +"arrow" -> Type=3447, Amount=10, Price=20, "Do you want to buy %A arrows for %P gold?", Topic=34 +"bolt" -> Type=3446, Amount=10, Price=30, "Do you want to buy %A bolts for %P gold?", Topic=34 +%1,0<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=34 +%1,0<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=34 + +Topic=34,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=34,"yes" -> "Come back, when you have enough money." +Topic=34 -> "Hmm, but next time." diff --git a/data/npc/gen-t-fruit-s.ndb b/data/npc/gen-t-fruit-s.ndb new file mode 100644 index 0000000..d9537ce --- /dev/null +++ b/data/npc/gen-t-fruit-s.ndb @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTfruitV.ndb: Datenbank für generischen Früchteverkauf + + +"corncob" -> Type=3597, Amount=1, Price=3, "Do you want to buy a corncob for %P gold?", Topic=24 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=24 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=24 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=24 +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=24 +"strawberry" -> Type=3591, Amount=1, Price=1, "Do you want to buy a strawberry for %P gold?", Topic=24 +"carrot" -> Type=3595, Amount=1, Price=3, "Do you want to buy a carrot for %P gold?", Topic=24 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=24 +"pear" -> Type=3584, Amount=1, Price=4, "Do you want to buy a pear for %P gold?", Topic=24 +"blueberry" -> Type=3588, Amount=1, Price=1, "Do you want to buy a blueberry for %P gold?", Topic=24 + +"white","mushroom" -> Type=3723, Amount=1, Price=10, "Do you want to buy one of the white mushrooms for %P gold?", Topic=24 + + +%1,1<%1,"corncob" -> Type=3597, Amount=%1, Price=3*%1, "Do you want to buy %A corncobs for %P gold?", Topic=24 +%1,1<%1,"cherry" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherrys for %P gold?", Topic=24 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=24 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=24 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=24 +%1,1<%1,"strawberries" -> Type=3591, Amount=%1, Price=1*%1, "Do you want to buy %A strawberries for %P gold?", Topic=24 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=3*%1, "Do you want to buy %A carrots for %P gold?", Topic=24 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=24 +%1,1<%1,"pear" -> Type=3584, Amount=%1, Price=4*%1, "Do you want to buy %A pears for %P gold?", Topic=24 +%1,1<%1,"blueberries" -> Type=3588, Amount=%1, Price=1*%1, "Do you want to buy %A blueberries for %P gold?", Topic=24 + + +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=10*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=24 + +Topic=24,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=24,"yes" -> "Sorry, you do not have enough gold." +Topic=24 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-furniture-chairs-s.ndb b/data/npc/gen-t-furniture-chairs-s.ndb new file mode 100644 index 0000000..bc1b029 --- /dev/null +++ b/data/npc/gen-t-furniture-chairs-s.ndb @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-chairs-s.ndb: Datenbank für generischen Möbelverkauf - Stühle +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"equipment" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"red", "cushioned",! -> Type=2775, Amount=1, Price=40, "You want to buy a red cushioned chair for %P gold?", Topic=81 +"red", "chair",! -> * +"green", "cushioned",! -> Type=2776, Amount=1, Price=40, "You want to buy a green cushioned chair for %P gold?", Topic=81 +"green", "chair",! -> * +"chair" -> "I can offer you wooden chairs, rocking chairs, red cushioned chairs, green cushioned chairs and sofa chairs." +"wooden", "chair" -> Type=2777, Amount=1, Price=15, "You want to buy a wooden chair for %P gold?", Topic=81 +"rocking", "chair" -> Type=2778, Amount=1, Price=25, "You want to buy a rocking chair for %P gold?", Topic=81 +"cushioned", "chair" -> "I can offer you a red cushioned chair or a green cushioned chair." +"sofa", "chair" -> Type=2779, Amount=1, Price=55, "You want to buy a sofa chair for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-containers-s.ndb b/data/npc/gen-t-furniture-containers-s.ndb new file mode 100644 index 0000000..b090809 --- /dev/null +++ b/data/npc/gen-t-furniture-containers-s.ndb @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-containers-s.ndb: Datenbank für generischen Möbelverkauf - Container +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"containers" -> "I offer drawers, dressers, lockers, crates, chests, boxes, barrels, trunks and troughs." +"drawer" -> Type=2789, Amount=1, Price=18, "You want to buy drawers for %P gold?", Topic=81 +"dresser" -> Type=2790, Amount=1, Price=25, "You want to buy a dresser for %P gold?", Topic=81 +"locker" -> Type=2791, Amount=1, Price=30, "You want to buy a locker for %P gold?", Topic=81 +"crate" -> Type=2471, Amount=1, Price=10, "Do you want to buy a crate for %P gold?", Topic=81 +"chest" -> Type=2472, Amount=1, Price=10, "Do you want to buy a chest for %P gold?", Topic=81 +"box" -> Type=2469, Amount=1, Price=10, "Do you want to buy a box for %P gold?", Topic=81 +"barrel" -> Type=2793, Amount=1, Price=12, "Do you want to buy a barrel for %P gold?", Topic=81 +"trough" -> Type=2792, Amount=1, Price=7, "Do you want to buy a trough for %P gold?", Topic=81 +"trunk" -> Type=2794, Amount=1, Price=10, "Do you want to buy a trunk for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-decoration-s.ndb b/data/npc/gen-t-furniture-decoration-s.ndb new file mode 100644 index 0000000..17cae22 --- /dev/null +++ b/data/npc/gen-t-furniture-decoration-s.ndb @@ -0,0 +1,33 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-decoration-s.ndb: Datenbank für generischen Möbelverkauf - Dekoration +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"decoration" -> "I can offer water pipes, pendulum clock, telescopes, table lamps, rocking horses, globes and birdcages. I also sell wall hangings." +"water", "pipe" -> Type=2974, Amount=1, Price=40, "You want to buy a water pipe for %P gold?", Topic=81 +"pendulum", "clock" -> Type=2801, Amount=1, Price=75, "You want to buy a pendulum clock for %P gold?", Topic=81 +"telescope" -> Type=2799, Amount=1, Price=70, "You want to buy a telescope for %P gold?", Topic=81 +"table","lamp" -> Type=2798, Amount=1, Price=35, "You want to buy a table lamp for %P gold?", Topic=81 +"rocking","horse" -> Type=2800, Amount=1, Price=30, "You want to buy a rocking horse for %P gold?", Topic=81 +"globe" -> Type=2797, Amount=1, Price=50, "You want to buy a globe for %P gold?", Topic=81 +"birdcage" -> Type=2796, Amount=1, Price=50, "You want to buy a birdcage for %P gold?", Topic=81 +"wall","hangings" -> "I can offer mirrors, paintings and cuckoo clocks." +"cuckoo" -> Type=2664, Amount=1, Price=40, "You want to buy a cuckoo clock for %P gold?", Topic=81 +"painting" -> "Would you like a landscape, a portrait or a still life. What is your choice?" +"portrait" -> Type=2641, Amount=1, Price=50, "You want to buy a portrait picture for %P gold?", Topic=81 +"landscape" -> Type=2639, Amount=1, Price=50, "You want to buy a landscape picture for %P gold?", Topic=81 +"still", "life" -> Type=2640, Amount=1, Price=50, "You want to buy a still life picture for %P gold?", Topic=81 +"mirror" -> "I sell round mirrors, oval mirrors and edged mirrors. Which one might it be?" +"round", "mirror" -> Type=2632, Amount=1, Price=40, "You want to buy a round mirror for %P gold?", Topic=81 +"oval", "mirror" -> Type=2638, Amount=1, Price=40, "You want to buy a oval mirror for %P gold?", Topic=81 +"edged", "mirror" -> Type=2635, Amount=1, Price=40, "You want to buy a edged mirror for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-flowers-s.ndb b/data/npc/gen-t-furniture-flowers-s.ndb new file mode 100644 index 0000000..768732b --- /dev/null +++ b/data/npc/gen-t-furniture-flowers-s.ndb @@ -0,0 +1,23 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-flowers-s.ndb: Datenbank für generischen Möbelverkauf - Pflanzen und Blumen +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"flower" -> "I offer indoor plants, flower bowls, god flowers, honey flowers and potted flowers. What do you need?" +"plant" -> * +"god","flower" -> Type=2981, Amount=1, Price=5, "Do you want to buy god flowers for %P gold?", Topic=81 +"indoor","plant" -> Type=2811, Amount=1, Price=8, "Do you want to buy an indoor plant for %P gold?", Topic=81 +"flower","bowl" -> Type=2983, Amount=1, Price=6, "Do you want to buy a flower bowl for %P gold?", Topic=81 +"honey","flower" -> Type=2984, Amount=1, Price=5, "Do you want to buy a honey flower for %P gold?", Topic=81 +"potted","flower" -> Type=2985, Amount=1, Price=5, "Do you want to buy a potted flower for %P gold?", Topic=81 +# "christmas","tree" -> Type=2812, Amount=1, Price=50, "A christmas tree is a very nice decoration for your house. Unfortunately it passes off in some weeks! Do you want to buy one for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-instruments-s.ndb b/data/npc/gen-t-furniture-instruments-s.ndb new file mode 100644 index 0000000..24d4854 --- /dev/null +++ b/data/npc/gen-t-furniture-instruments-s.ndb @@ -0,0 +1,18 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-instruments-s.ndb: Datenbank für generischen Möbelverkauf - Instrumente +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"instruments" -> "I can offer you a piano or a harp. What would you like?" +"piano" -> Type=2807, Amount=1, Price=200, "You want to buy a piano for %P gold?", Topic=81 +"harp" -> Type=2808, Amount=1, Price=50, "You want to buy a harp for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-jungle-s.ndb b/data/npc/gen-t-furniture-jungle-s.ndb new file mode 100644 index 0000000..688268e --- /dev/null +++ b/data/npc/gen-t-furniture-jungle-s.ndb @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-jungle-s.ndb: Datenbank für generischen Möbelverkauf - Bambusmöbel +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"equipment" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"chair" -> "I can offer you tusk chairs, ivory chairs and trunk chairs." +"tusk", "chair" -> Type=2780, Amount=1, Price=25, "You want to buy a tusk chair for %P gold?", Topic=81 +"ivory", "chair" -> Type=2781, Amount=1, Price=25, "You want to buy an ivory chair for %P gold?", Topic=81 +"trunk", "chair" -> Type=2809, Amount=1, Price=20, "You want to buy a trunk chair for %P gold?", Topic=81 + +"table" -> "I can offer you stone tables, tusk tables, bamboo tables and trunk tables." +"stone", "table" -> Type=2786, Amount=1, Price=30, "You want to buy a stone table for %P gold?", Topic=81 +"tusk", "table" -> Type=2787, Amount=1, Price=25, "You want to buy a tusk table for %P gold?", Topic=81 +"bamboo", "table" -> Type=2788, Amount=1, Price=25, "You want to buy a bamboo table for %P gold?", Topic=81 +"trunk", "table" -> Type=2810, Amount=1, Price=20, "You want to buy a trunk table for %P gold?", Topic=81 + +"drawer" -> "I can offer you bamboo drawers." +"bamboo", "drawer" -> Type=2795, Amount=1, Price=20, "You want to buy a bamboo drawer for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-pillows-s.ndb b/data/npc/gen-t-furniture-pillows-s.ndb new file mode 100644 index 0000000..bc00031 --- /dev/null +++ b/data/npc/gen-t-furniture-pillows-s.ndb @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-pillows-s.ndb: Datenbank für generischen Möbelverkauf - Kissen +# Verwendete Topics: 81 bis 84 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"pillow" -> "I can offer small pillows, round pillows, square pillows and heart pillows. Which one might it be?" +"small", "pillow" -> "What color would you prefer? Purple, green, red, blue, orange, turquoise or white?", Topic=82 +Topic=82, "purple" -> Type=2386, Amount=1, Price=20, "You want to buy a small, purple pillow for %P gold?", Topic=81 +Topic=82, "green" -> Type=2387, Amount=1, Price=20, "You want to buy a small, green pillow for %P gold?", Topic=81 +Topic=82, "red" -> Type=2388, Amount=1, Price=20, "You want to buy a small, red pillow for %P gold?", Topic=81 +Topic=82, "blue" -> Type=2389, Amount=1, Price=20, "You want to buy a small, blue pillow for %P gold?", Topic=81 +Topic=82, "orange" -> Type=2390, Amount=1, Price=20, "You want to buy a small, orange pillow for %P gold?", Topic=81 +Topic=82, "turquoise" -> Type=2391, Amount=1, Price=20, "You want to buy a small, turquoise pillow for %P gold?", Topic=81 +Topic=82, "white" -> Type=2392, Amount=1, Price=20, "You want to buy a small, white pillow for %P gold?", Topic=81 +"round", "pillow" -> "What color would you prefer? Purple, red, blue or turquoise?", Topic=83 +Topic=83, "blue" -> Type=2398, Amount=1, Price=25, "You want to buy a blue, round pillow for %P gold?", Topic=81 +Topic=83, "purple" -> Type=2400, Amount=1, Price=25, "You want to buy a purple, round pillow for %P gold?", Topic=81 +Topic=83, "red" -> Type=2399, Amount=1, Price=25, "You want to buy a red, round pillow for %P gold?", Topic=81 +Topic=83, "turquoise" -> Type=2401, Amount=1, Price=25, "You want to buy a turquoise, round pillow for %P gold?", Topic=81 +"square", "pillow" -> "What color would you prefer? Red, green, blue or yellow?", Topic=84 +Topic=84, "blue" -> Type=2394, Amount=1, Price=25, "You want to buy a blue, square pillow for %P gold?", Topic=81 +Topic=84, "red" -> Type=2395, Amount=1, Price=25, "You want to buy a red, square pillow for %P gold?", Topic=81 +Topic=84, "green" -> Type=2396, Amount=1, Price=25, "You want to buy a green, square pillow for %P gold?", Topic=81 +Topic=84, "yellow" -> Type=2397, Amount=1, Price=25, "You want to buy a yellow, square pillow for %P gold?", Topic=81 +"heart", "pillow" -> Type=2393, Amount=1, Price=30, "You want to buy a heart pillow for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-pottery-s.ndb b/data/npc/gen-t-furniture-pottery-s.ndb new file mode 100644 index 0000000..ef83fa2 --- /dev/null +++ b/data/npc/gen-t-furniture-pottery-s.ndb @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-pottery-s.ndb: Datenbank für generischen Möbelverkauf - Töpfe +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"pottery" -> "I offer vases, coal basins, amphora and large amphora. What do you need?" +"vase" -> Type=2876, Amount=1, Price=3, "Do you want to buy a vase for %P gold?", Topic=81 +"large", "amphora" -> Type=2805, Amount=1, Price=50, "Do you want to buy a large amphora for %P gold?", Topic=81 +"amphora" -> Type=2893, Amount=1, Price=4, "Do you want to buy an amphora for %P gold?", Topic=81 +"coal","basin" -> Type=2806, Amount=1, Price=25, "Do you want to buy a coal basin for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-statues-s.ndb b/data/npc/gen-t-furniture-statues-s.ndb new file mode 100644 index 0000000..6046b9b --- /dev/null +++ b/data/npc/gen-t-furniture-statues-s.ndb @@ -0,0 +1,19 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-statues-s.ndb: Datenbank für generischen Möbelverkauf - Statuen +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"statue" -> "What statue would you like? A knights statue, a minotaur statue or a goblin statue?" +"knight", "statue" -> Type=2802, Amount=1, Price=50, "Do you want to buy this wonderful statue for %P gold?", Topic=81 +"minotaur", "statue" -> Type=2803, Amount=1, Price=50, "Do you want to buy this frigtening statue for %P gold?", Topic=81 +"goblin", "statue" -> Type=2804, Amount=1, Price=50, "Do you want to buy this disgusting statue for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-tables-s.ndb b/data/npc/gen-t-furniture-tables-s.ndb new file mode 100644 index 0000000..45690c8 --- /dev/null +++ b/data/npc/gen-t-furniture-tables-s.ndb @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-tables-s.ndb: Datenbank für generischen Möbelverkauf - Tische +# Verwendete Topics: 81 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"table" -> "Do you want to buy a small table, a round table, a square table or a big table?" +"small", "table" -> Type=2782, Amount=1, Price=20, "Do you want to buy a small table for %P gold?", Topic=81 +"round", "table" -> Type=2783, Amount=1, Price=25, "Do you want to buy a round table for %P gold?", Topic=81 +"square", "table" -> Type=2784, Amount=1, Price=25, "Do you want to buy a square table for %P gold?", Topic=81 +"big", "table" -> Type=2785, Amount=1, Price=30, "Do you want to buy a big table for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-furniture-tapestries-s.ndb b/data/npc/gen-t-furniture-tapestries-s.ndb new file mode 100644 index 0000000..73a264e --- /dev/null +++ b/data/npc/gen-t-furniture-tapestries-s.ndb @@ -0,0 +1,26 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-furniture-tapestries-s.ndb: Datenbank für generischen Möbelverkauf - Vorhänge +# Verwendete Topics: 81, 85 + +#"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +#"furniture" -> * +#"goods" -> * +#"do","you","sell" -> * +#"do","you","have" -> * + +"curtain" -> "Well, actually it's better to call them tapestries." +"tapestr" -> "Please tell me what color you would prefer: purple, green, yellow, orange, red, blue or white?", Topic=85 +Topic=85, "purple" -> Type=2644, Amount=1, Price=25, "You want to buy a purple tapestry for %P gold?", Topic=81 +Topic=85, "green" -> Type=2647, Amount=1, Price=25, "You want to buy a green tapestry for %P gold?", Topic=81 +Topic=85, "yellow" -> Type=2650, Amount=1, Price=25, "You want to buy a yellow tapestry for %P gold?", Topic=81 +Topic=85, "orange" -> Type=2653, Amount=1, Price=25, "You want to buy a orange tapestry for %P gold?", Topic=81 +Topic=85, "red" -> Type=2656, Amount=1, Price=25, "You want to buy a red tapestry for %P gold?", Topic=81 +Topic=85, "orange" -> Type=2653, Amount=1, Price=25, "You want to buy a orange tapestry for %P gold?", Topic=81 +Topic=85, "red" -> Type=2656, Amount=1, Price=25, "You want to buy a red tapestry for %P gold?", Topic=81 +Topic=85, "blue" -> Type=2659, Amount=1, Price=25, "You want to buy a blue tapestry for %P gold?", Topic=81 +Topic=85, "white" -> Type=2667, Amount=1, Price=25, "You want to buy a white tapestry for %P gold?", Topic=81 + +Topic=81,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=81,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=81,"yes" -> "Come back, when you have enough money." +Topic=81 -> "Hmm, but I'm sure, it would fit nicely into your house." diff --git a/data/npc/gen-t-gear-s.ndb b/data/npc/gen-t-gear-s.ndb new file mode 100644 index 0000000..67ade3c --- /dev/null +++ b/data/npc/gen-t-gear-s.ndb @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTgearV.ndb: Datenbank für generischen Ausrüstungsverkauf + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=25 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=25 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=25 +"bag" -> Type=2862, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=25 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=25 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=25 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=25 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=25 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=25 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=25 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=25 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=25 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=25 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=25 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a dwarfish steel crowbar for %P gold?", Topic=25 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you want to' buy a water hose for %P gold?", Topic=25 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=25 +"bucket" -> Type=2873, Amount=1, Price=4, Data=0, "Do you want to buy a bucket for %P gold?", Topic=25 +"bottle" -> Type=2875, Amount=1, Price=3, Data=0, "Do you want to buy a bottle for %P gold?", Topic=25 +%1,0<%1,"torches" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=25 + +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=26 + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=27 +"vial" -> * +"flask" -> * + +Topic=25,"yes",CountMoney>=Price -> "Here it is!", DeleteMoney, Create(Type) +Topic=25,"yes" -> "Sorry, you have no money!" +Topic=25 -> "Perhaps another time." + +Topic=26,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=26,"yes" -> "Sorry, you have no money!" +Topic=26 -> "Perhaps another time." + + +Topic=27,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=27,"yes" -> "You don't have any empty vials." +Topic=27 -> "Hmm, but please keep this place litter free." + diff --git a/data/npc/gen-t-gems-s.ndb b/data/npc/gen-t-gems-s.ndb new file mode 100644 index 0000000..9b88153 --- /dev/null +++ b/data/npc/gen-t-gems-s.ndb @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-gems-s.ndb: Datenbank für generischen Edelsteinverkauf + + + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and 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." + +"wedding","ring" -> Type=3004, Amount=1, Price=990, "Do you want to buy a wedding ring for %P gold?", Topic=29 +"golden","amulet" -> Type=3013, Amount=1, Price=6600, "Do you want to buy a golden amulet for %P gold?", Topic=29 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=29 +"white","pearl" -> Type=3026, Amount=1, Price=320, "Do you want to buy a white pearl for %P gold?", Topic=29 +"black","pearl" -> Type=3027, Amount=1, Price=560, "Do you want to buy a black pearl for %P gold?", Topic=29 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=29 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=29 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=29 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=29 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=29 + +Topic=29,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=29,"yes" -> "Come back, when you have enough money." +Topic=29 -> "Hmm, but next time." + diff --git a/data/npc/gen-t-helm-b.ndb b/data/npc/gen-t-helm-b.ndb new file mode 100644 index 0000000..0b32d91 --- /dev/null +++ b/data/npc/gen-t-helm-b.ndb @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-helm-b.ndb: Datenbank für generischen Helmeinkauf + +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=50 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=50 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=50 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=50 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=50 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "You want sell a iron helmet for %P gold?", Topic=50 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "You want sell a devil's helmet for %P gold?", Topic=50 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "You want sell a warrior helmet for %P gold?", Topic=50 + +"sell",%1,1<%1,"leather","helm" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"chain","helm" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"steel","helm" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"brass","helm" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"viking","helm" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "You want sell %A iron helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "You want sell %A devil's helmets for %P gold?", Topic=50 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "You want sell %A warrior helmets for %P gold?", Topic=50 + + + +Topic=50,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=50,"yes" -> "Sorry, you do not have one." +Topic=50,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=50 -> "Maybe next time." + diff --git a/data/npc/gen-t-helm-s.ndb b/data/npc/gen-t-helm-s.ndb new file mode 100644 index 0000000..789377a --- /dev/null +++ b/data/npc/gen-t-helm-s.ndb @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genThelmV.ndb: Datenbank für generischen Helmverkauf + +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=20 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=20 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=21 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "Do you want to buy a brass helmet for %P gold?", Topic=21 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=21 + + +Topic=20,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=20,"yes" -> "Sorry, you do not have enough gold." +Topic=20 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-legs-s.ndb b/data/npc/gen-t-legs-s.ndb new file mode 100644 index 0000000..592be82 --- /dev/null +++ b/data/npc/gen-t-legs-s.ndb @@ -0,0 +1,13 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTlegsV.ndb: Datenbank für generischen Legsverkauf + +"leather","boot" -> Type=3552, Amount=1, Price=2, "Do you want to buy one of my wonderful leather boots for %P gold?", Topic=23 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=23 +"studded","legs" -> Type=3362, Amount=1, Price=60, "Do you want to buy studded legs for %P gold?", Topic=23 +"chain","legs" -> Type=3558, Amount=1, Price=80, "You want buy chain legs for %P gold?", Topic=23 +"brass","legs" -> Type=3372, Amount=1, Price=195, "You want buy brass legs for %P gold?", Topic=23 + + +Topic=23,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=23,"yes" -> "Sorry, you do not have enough gold." +Topic=23 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-magic-s.ndb b/data/npc/gen-t-magic-s.ndb new file mode 100644 index 0000000..fc06e31 --- /dev/null +++ b/data/npc/gen-t-magic-s.ndb @@ -0,0 +1,31 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-gems-s.ndb: Datenbank für generischen Edelsteinverkauf + +"offer" -> "I'm selling life and mana fluids, runes, and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=30 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=100, "Do you want to buy mana fluid for %P gold?", Topic=30 +"rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=31 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=31 +%1,0<%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=30 +%1,0<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=100*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=30 +%1,0<%1,"runes" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=31 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=32 +"vial" -> * +"flask" -> * + +Topic=31,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=31,"yes" -> "Come back, when you have enough money." +Topic=31 -> "Hmm, but next time." + +Topic=30,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=30,"yes" -> "Come back, when you have enough money." +Topic=30 -> "Hmm, but next time." + +Topic=32,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=32,"yes" -> "You don't have any empty vials." +Topic=32 -> "Hmm, but please keep Tibia litter free." diff --git a/data/npc/gen-t-meat-s.ndb b/data/npc/gen-t-meat-s.ndb new file mode 100644 index 0000000..ecf8944 --- /dev/null +++ b/data/npc/gen-t-meat-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-tmeat-s.ndb: Datenbank für generischen Fleischverkauf + + +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=39 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=39 + + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=39 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=39 + +Topic=39,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=39,"yes" -> "I am sorry, but you do not have enough gold." +Topic=39 -> "Maybe later." diff --git a/data/npc/gen-t-music-s.ndb b/data/npc/gen-t-music-s.ndb new file mode 100644 index 0000000..f8ca119 --- /dev/null +++ b/data/npc/gen-t-music-s.ndb @@ -0,0 +1,17 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTshieldV.ndb: Datenbank für generischen Instrumentenverkauf + +"offer" -> "You can buy a lyre, lute, drum, and simple fanfare." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=38 +"lute" -> Type=2950, Amount=1, Price=195, "Do you want to buy a lute for %P gold?", Topic=38 +"drum" -> Type=2952, Amount=1, Price=140, "Do you want to buy a drum for %P gold?", Topic=38 +"simple","fanfare" -> Type=2954, Amount=1, Price=150, "Do you want to buy a simple fanfare for %P gold?", Topic=38 + +Topic=38,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=38,"yes" -> "Come back, when you have enough money." +Topic=38 -> "Hmm, but next time." diff --git a/data/npc/gen-t-runes-free-s.ndb b/data/npc/gen-t-runes-free-s.ndb new file mode 100644 index 0000000..3daddef --- /dev/null +++ b/data/npc/gen-t-runes-free-s.ndb @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-runes-free-s.ndb: Datenbank für generischen Runenverkauf - Free Account Runen +# Verwendete Topics: 99 + +"spell","rune" -> "I sell missile runes, explosive runes, field runes, wall runes, bomb runes, healing runes, convince creature runes and chameleon runes." +"missile","rune" -> "I can offer you light magic missile runes, heavy magic missile runes and sudden death runes." +"explosive","rune" -> "I can offer you fireball runes, great fireball runes and explosion runes." +"field","rune" -> "I can offer you fire field runes, energy field runes, poison field runes and destroy field runes." +"wall","rune" -> "I can offer you fire wall runes, energy wall runes and poison wall runes." +"bomb","rune" -> "I can offer you firebomb runes." +"healing","rune" -> "I can offer you antidote runes, intense healing runes and ultimate healing runes." + +"light","magic","missile","rune" -> Type=3174, Data=5, Amount=1, Price=40, "Do you want to buy a light magic missile rune for %P gold?", Topic=99 +"poison","field","rune" -> Type=3172, Data=3, Amount=1, Price=65, "Do you want to buy a poison field rune for %P gold?", Topic=99 +"antidote","rune" -> Type=3153, Data=1, Amount=1, Price=65, "Do you want to buy an antidote rune for %P gold?", Topic=99 +"fire","field","rune" -> Type=3188, Data=3, Amount=1, Price=85, "Do you want to buy a fire field rune for %P gold?", Topic=99 +"intense","healing","rune" -> Type=3152, Data=1, Amount=1, Price=95, "Do you want to buy an intense healing rune for %P gold?", Topic=99 +"fireball","rune" -> Type=3189, Data=2, Amount=1, Price=95, "Do you want to buy a fireball rune for %P gold?", Topic=99 +"destroy","field","rune" -> Type=3148, Data=3, Amount=1, Price=45, "Do you want to buy a destroy field rune for %P gold?", Topic=99 +"heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=1, Price=125, "Do you want to buy a heavy magic missile rune for %P gold?", Topic=99 +"energy","field","rune" -> Type=3164, Data=3, Amount=1, Price=115, "Do you want to buy an energy field rune for %P gold?", Topic=99 +"ultimate","healing","rune" -> Type=3160, Data=1, Amount=1, Price=175, "Do you want to buy an ultimate healing rune for %P gold?", Topic=99 +"convince","creature","rune" -> Type=3177, Data=1, Amount=1, Price=80, "Do you want to buy a convince creature rune for %P gold?", Topic=99 +"great","fireball","rune" -> Type=3191, Data=2, Amount=1, Price=180, "Do you want to buy a great fireball rune for %P gold?", Topic=99 +"chameleon","rune" -> Type=3178, Data=1, Amount=1, Price=210, "Do you want to buy a chameleon rune for %P gold?", Topic=99 +"fire","bomb","rune" -> Type=3192, Data=2, Amount=1, Price=235, "Do you want to buy a firebomb rune for %P gold?", Topic=99 +"poison","wall","rune" -> Type=3176, Data=4, Amount=1, Price=210, "Do you want to buy a poison wall rune for %P gold?", Topic=99 +"explosion","rune" -> Type=3200, Data=3, Amount=1, Price=250, "Do you want to buy an explosion rune for %P gold?", Topic=99 +"fire","wall","rune" -> Type=3190, Data=4, Amount=1, Price=245, "Do you want to buy a fire wall rune for %P gold?", Topic=99 +"sudden","death","rune" -> Type=3155, Data=1, Amount=1, Price=325, "Do you want to buy a sudden death rune for %P gold?", Topic=99 +"energy","wall","rune" -> Type=3166, Data=4, Amount=1, Price=340, "Do you want to buy an energy wall rune for %P gold?", Topic=99 + +"envenom","rune" -> "Sorry, but runes of this type can't be purchased here." +"desintegrate","rune" -> * +"poison","bomb","rune" -> * +"soulfire","rune" -> * +"energy","bomb","rune" -> * +"magic","wall","rune" -> * +"animate","dead","rune" -> * +"paralyze","rune" -> * + +%1,1<%1,"light","magic","missile","rune" -> Type=3174, Data=5, Amount=%1, Price=40*%1, "Do you want to buy %A light magic missile runes for %P gold?", Topic=99 +%1,1<%1,"poison","field","rune" -> Type=3172, Data=3, Amount=%1, Price=65*%1, "Do you want to buy %A poison field runes for %P gold?", Topic=99 +%1,1<%1,"antidote","rune" -> Type=3153, Data=1, Amount=%1, Price=65*%1, "Do you want to buy %A antidote runes for %P gold?", Topic=99 +%1,1<%1,"fire","field","rune" -> Type=3188, Data=3, Amount=%1, Price=85*%1, "Do you want to buy %A fire field runes for %P gold?", Topic=99 +%1,1<%1,"intense","healing","rune" -> Type=3152, Data=1, Amount=%1, Price=95*%1, "Do you want to buy %A intense healing runes for %P gold?", Topic=99 +%1,1<%1,"fireball","rune" -> Type=3189, Data=2, Amount=%1, Price=95*%1, "Do you want to buy %A fireball runes for %P gold?", Topic=99 +%1,1<%1,"destroy","field","rune" -> Type=3148, Data=3, Amount=%1, Price=45*%1, "Do you want to buy %A destroy field runes for %P gold?", Topic=99 +%1,1<%1,"heavy","magic","missile","rune" -> Type=3198, Data=5, Amount=%1, Price=125*%1, "Do you want to buy %A heavy magic missile runes for %P gold?", Topic=99 +%1,1<%1,"energy","field","rune" -> Type=3164, Data=3, Amount=%1, Price=115*%1, "Do you want to buy %A energy field runes for %P gold?", Topic=99 +%1,1<%1,"ultimate","healing","rune" -> Type=3160, Data=1, Amount=%1, Price=175*%1, "Do you want to buy %A ultimate healing runes for %P gold?", Topic=99 +%1,1<%1,"convince","creature","rune" -> Type=3177, Data=1, Amount=%1, Price=80*%1, "Do you want to buy %A convince creature runes for %P gold?", Topic=99 +%1,1<%1,"great","fireball","rune" -> Type=3191, Data=2, Amount=%1, Price=180*%1, "Do you want to buy %A great fireball runes for %P gold?", Topic=99 +%1,1<%1,"chameleon","rune" -> Type=3178, Data=1, Amount=%1, Price=210*%1, "Do you want to buy %A chameleon runes for %P gold?", Topic=99 +%1,1<%1,"fire","bomb","rune" -> Type=3192, Data=2, Amount=%1, Price=235*%1, "Do you want to buy %A firebomb runes for %P gold?", Topic=99 +%1,1<%1,"poison","wall","rune" -> Type=3176, Data=4, Amount=%1, Price=210*%1, "Do you want to buy %A poison wall runes for %P gold?", Topic=99 +%1,1<%1,"explosion","rune" -> Type=3200, Data=3, Amount=%1, Price=250*%1, "Do you want to buy %A explosion runes for %P gold?", Topic=99 +%1,1<%1,"fire","wall","rune" -> Type=3190, Data=4, Amount=%1, Price=245*%1, "Do you want to buy %A fire wall runes for %P gold?", Topic=99 +%1,1<%1,"sudden","death","rune" -> Type=3155, Data=1, Amount=%1, Price=325*%1, "Do you want to buy %A sudden death runes for %P gold?", Topic=99 +%1,1<%1,"energy","wall","rune" -> Type=3166, Data=4, Amount=%1, Price=340*%1, "Do you want to buy %A energy wall runes for %P gold?", Topic=99 + + +Topic=99,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=99,"yes" -> "Sorry, you don't have enough gold." +Topic=99 -> "As you wish." diff --git a/data/npc/gen-t-runes-prem-s.ndb b/data/npc/gen-t-runes-prem-s.ndb new file mode 100644 index 0000000..2c891ba --- /dev/null +++ b/data/npc/gen-t-runes-prem-s.ndb @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-runes-prem-s.ndb: Datenbank für generischen Runenverkauf - Premium Account Runen +# Verwendete Topics: 99 + +"spell","rune" -> "I sell envenom runes, desintegrate runes, poison bomb runes, energy bomb runes, soulfire runes, magic wall runes, animate dead runes and paralyze runes." + +"envenom","rune" -> Type=3179, Data=3, Amount=1, Price=130, "Do you want to buy an envenom rune for %P gold?", Topic=99 +"desintegrate","rune" -> Type=3197, Data=3, Amount=1, Price=80, "Do you want to buy a desintegrate rune for %P gold?", Topic=99 +"poison","bomb","rune" -> Type=3173, Data=2, Amount=1, Price=170, "Do you want to buy a poison bomb rune for %P gold?", Topic=99 +"soulfire","rune" -> Type=3195, Data=2, Amount=1, Price=210, "Do you want to buy a soulfire rune for %P gold?", Topic=99 +"energy","bomb","rune" -> Type=3149, Data=2, Amount=1, Price=325, "Do you want to buy an energybomb rune for %P gold?", Topic=99 +"magic","wall","rune" -> Type=3180, Data=3, Amount=1, Price=350, "Do you want to buy a magic wall rune for %P gold?", Topic=99 +"animate","dead","rune" -> Type=3203, Data=1, Amount=1, Price=375, "Do you want to buy an animate dead rune for %P gold?", Topic=99 +"paralyze","rune" -> Type=3165, Data=1, Amount=1, Price=700, "Do you want to buy a paralyze rune for %P gold?", Topic=99 + + +%1,1<%1,"envenom","rune" -> Type=3179, Data=3, Amount=%1, Price=130*%1, "Do you want to buy %A envenom runes for %P gold?", Topic=99 +%1,1<%1,"desintegrate","rune" -> Type=3197, Data=3, Amount=%1, Price=80*%1, "Do you want to buy %A desintegrate runes for %P gold?", Topic=99 +%1,1<%1,"poison","bomb","rune" -> Type=3173, Data=2, Amount=%1, Price=170*%1, "Do you want to buy %A poison bomb runes for %P gold?", Topic=99 +%1,1<%1,"soulfire","rune" -> Type=3195, Data=2, Amount=%1, Price=210*%1, "Do you want to buy %A soulfire runes for %P gold?", Topic=99 +%1,1<%1,"energy","bomb","rune" -> Type=3149, Data=2, Amount=%1, Price=325*%1, "Do you want to buy %A energybomb runes for %P gold?", Topic=99 +%1,1<%1,"magic","wall","rune" -> Type=3180, Data=3, Amount=%1, Price=350*%1, "Do you want to buy %A magic wall runes for %P gold?", Topic=99 +%1,1<%1,"animate","dead","rune" -> Type=3203, Data=1, Amount=%1, Price=375*%1, "Do you want to buy %A animate dead runes for %P gold?", Topic=99 +%1,1<%1,"paralyze","rune" -> Type=3165, Data=1, Amount=%1, Price=700*%1, "Do you want to buy %A paralyze runes for %P gold?", Topic=99 + + +"light","magic","missile","rune" -> "Sorry, but runes of this type can't be purchased here." +"poison","field","rune" -> * +"antidote","rune" -> * +"fire","field","rune" -> * +"intense","healing","rune" -> * +"fireball","rune" -> * +"destroy","field","rune" -> * +"heavy","magic","missile","rune" -> * +"energy","field","rune" -> * +"ultimate","healing","rune" -> * +"convince","creature","rune" -> * +"great","fireball","rune" -> * +"chameleon","rune" -> * +"fire","bomb","rune" -> * +"poison","wall","rune" -> * +"explosion","rune" -> * +"fire","wall","rune" -> * +"sudden","death","rune" -> * +"energy","wall","rune" -> * + + +Topic=99,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=99,"yes" -> "Sorry, you don't have enough gold." +Topic=99 -> "As you wish." diff --git a/data/npc/gen-t-shield-b.ndb b/data/npc/gen-t-shield-b.ndb new file mode 100644 index 0000000..39339eb --- /dev/null +++ b/data/npc/gen-t-shield-b.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-shield-b.ndb: Datenbank für generischen Schildkauf + +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=52 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=52 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=52 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=52 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "Do you want to sell a plate shield for %P gold?", Topic=52 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=100, "You want sell a dwarven shield for %P gold?", Topic=52 +"sell","guardians","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell this for %P gold?", Topic=52 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell this for %P gold?", Topic=52 + + +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell those wooden shields for %P gold?", Topic=52 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell those battle shields for %P gold?", Topic=52 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell those steel shields for %P gold?", Topic=52 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell those brass shields for %P gold?", Topic=52 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "Do you want to sell those plate shields for %P gold?", Topic=52 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=100*%1, "Do you want sell those dwarven shields for %P gold?", Topic=52 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell those guardian shields for %P gold?", Topic=52 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell those dragon shields for %P gold?", Topic=52 + + + +Topic=52,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=52,"yes" -> "Sorry, you do not have enough gold." +Topic=52 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-shield-s.ndb b/data/npc/gen-t-shield-s.ndb new file mode 100644 index 0000000..98174c3 --- /dev/null +++ b/data/npc/gen-t-shield-s.ndb @@ -0,0 +1,14 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# genTshieldV.ndb: Datenbank für generischen Schildverkauf + +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=22 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=22 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=22 +"brass","shield" -> Type=3411, Amount=1, Price=65, "Do you want to buy a brass shield for %P gold?", Topic=22 +"plate","shield" -> Type=3410, Amount=1, Price=125, "Do you want to buy a plate shield for %P gold?", Topic=22 +"dwarven","shield" -> Type=3425, Amount=1, Price=500, "Do you want to buy a dwarven shield for %P gold?", Topic=22 + + +Topic=22,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=22,"yes" -> "Sorry, you do not have enough gold." +Topic=22 -> "Maybe you will buy it another time." diff --git a/data/npc/gen-t-wands-free-s.ndb b/data/npc/gen-t-wands-free-s.ndb new file mode 100644 index 0000000..192013e --- /dev/null +++ b/data/npc/gen-t-wands-free-s.ndb @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-wands-free-s.ndb: Datenbank für generischen Zauberstabverkauf - Free Account Wands +# Verwendete Topics: 90,91,92 + +"wand" -> "Wands can be wielded by sorcerers only and have a certain level requirement. There are five different wands, would you like to hear about them?", Topic=90 +"rod" -> "Rods can be wielded by druids only and have a certain level requirement. There are five different rods, would you like to hear about them?", Topic=91 + +Topic=90,"yes" -> "The names of the wands are 'Wand of Vortex', 'Wand of Dragonbreath', 'Wand of Plague', 'Wand of Cosmic Energy' and 'Wand of Inferno'. Which one would you like to buy?" +Topic=90,"no" -> "Maybe another time." +Topic=90 -> "Maybe another time." + +Topic=91,"yes" -> "The names of the rods are 'Snakebite Rod', 'Moonlight Rod', 'Volcanic Rod', 'Quagmire Rod', and 'Tempest Rod'. Which one would you like to buy?" +Topic=91,"no" -> "Maybe another time." +Topic=91 -> "Maybe another time." + +sorcerer,"wand","of","vortex",QuestValue(333)<1 -> "Oh, is this your first wand of vortex? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"snakebite","rod",QuestValue(333)<1 -> "Oh, is this your first snakebite rod? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"wand","of","vortex" -> Type=3074, Amount=1, Price=500, "This wand is only for sorcerers of level 7 and above. Would you like to buy a wand of vortex for %P gold?", Topic=92 +"wand","of","dragonbreath" -> Type=3075, Amount=1, Price=1000, "This wand is only for sorcerers of level 13 and above. Would you like to buy a wand of dragonbreath for %P gold?", Topic=92 +"wand","of","plague" -> Type=3072, Amount=1, Price=5000, "This wand is only for sorcerers of level 19 and above. Would you like to buy a wand of plague for %P gold?", Topic=92 +"wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=10000, "This wand is only for sorcerers of level 26 and above. Would you like to buy a wand of cosmic energy for %P gold?", Topic=92 +"wand","of","inferno" -> "Sorry, this wand contains magic far too powerful and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + +"snakebite","rod" -> Type=3066, Amount=1, Price=500, "This rod is only for druids of level 7 and above. Would you like to buy a snakebite rod for %P gold?", Topic=92 +"moonlight","rod" -> Type=3070, Amount=1, Price=1000, "This rod is only for druids of level 13 and above. Would you like to buy a moonlight rod for %P gold?", Topic=92 +"volcanic","rod" -> Type=3069, Amount=1, Price=5000, "This rod is only for druids of level 19 and above. Would you like to buy a volcanic rod for %P gold?", Topic=92 +"quagmire","rod" -> Type=3065, Amount=1, Price=10000, "This rod is only for druids of level 26 and above. Would you like to buy a quagmire rod for %P gold?", Topic=92 +"tempest","rod" -> "Sorry, this rod contains magic far too powerful and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + +%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=500*%1, "This wand is only for sorcerers of level 7 and above. Would you like to buy %A wands of vortex for %P gold?", Topic=92 +%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=1000*%1, "This wand is only for sorcerers of level 13 and above. Would you like to buy %A wands of dragonbreath for %P gold?", Topic=92 +%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=5000*%1, "This wand is only for sorcerers of level 19 and above. Would you like to buy %A wands of plague for %P gold?", Topic=92 +%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=10000*%1, "This wand is only for sorcerers of level 26 and above. Would you like to buy %A wands of cosmic energy for %P gold?", Topic=92 +%1,1<%1,"wand","of","inferno" -> "Sorry, this wand contains far too powerful magic and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + + +%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=500*%1, "This rod is only for druids of level 7 and above. Would you like to buy %A snakebite rods for %P gold?", Topic=92 +%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=1000*%1, "This rod is only for druids of level 13 and above. Would you like to buy %A moonlight rods for %P gold?", Topic=92 +%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=5000*%1, "This rod is only for druids of level 19 and above. Would you like to buy %A volcanic rods for %P gold?", Topic=92 +%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=10000*%1, "This rod is only for druids of level 26 and above. Would you like to buy %A quagmire rods for %P gold?", Topic=92 +%1,1<%1,"tempest","rod" -> "Sorry, this rod contains far too powerful magic and we are afraid to store it here. I heard they have a few of these at the Edron academy though." + + +Topic=92,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=92,"yes" -> "Sorry, you don't have enough gold." +Topic=92 -> "You don't know what you're missing." \ No newline at end of file diff --git a/data/npc/gen-t-wands-prem-s.ndb b/data/npc/gen-t-wands-prem-s.ndb new file mode 100644 index 0000000..07fc81b --- /dev/null +++ b/data/npc/gen-t-wands-prem-s.ndb @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-wands-prem-s.ndb: Datenbank für generischen Zauberstabverkauf - Premium Account Wands +# Verwendete Topics: 90,91,92 + +"wand" -> "Wands can be wielded by sorcerers only and have a certain level requirement. There are five different wands, would you like to hear about them?", Topic=90 +"rod" -> "Rods can be wielded by druids only and have a certain level requirement. There are five different rods, would you like to hear about them?", Topic=91 + +Topic=90,"yes" -> "The names of the wands are 'Wand of Vortex', 'Wand of Dragonbreath', 'Wand of Plague', 'Wand of Cosmic Energy' and 'Wand of Inferno'. Which one would you like to buy?" +Topic=90,"no" -> "Maybe another time." +Topic=90 -> "Maybe another time." + +Topic=91,"yes" -> "The names of the rods are 'Snakebite Rod', 'Moonlight Rod', 'Volcanic Rod', 'Quagmire Rod', and 'Tempest Rod'. Which one would you like to buy?" +Topic=91,"no" -> "Maybe another time." +Topic=91 -> "Maybe another time." + +sorcerer,"wand","of","vortex",QuestValue(333)<1 -> "Oh, is this your first wand of vortex? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"snakebite","rod",QuestValue(333)<1 -> "Oh, is this your first snakebite rod? Take this little present from me as a free sample!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"wand","of","vortex" -> Type=3074, Amount=1, Price=500, "This wand is only for sorcerers of level 7 and above. Would you like to buy a wand of vortex for %P gold?", Topic=92 +"wand","of","dragonbreath" -> Type=3075, Amount=1, Price=1000, "This wand is only for sorcerers of level 13 and above. Would you like to buy a wand of dragonbreath for %P gold?", Topic=92 +"wand","of","plague" -> Type=3072, Amount=1, Price=5000, "This wand is only for sorcerers of level 19 and above. Would you like to buy a wand of plague for %P gold?", Topic=92 +"wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=10000, "This wand is only for sorcerers of level 26 and above. Would you like to buy a wand of cosmic energy for %P gold?", Topic=92 +"wand","of","inferno" -> Type=3071, Amount=1, Price=15000, "This wand is only for sorcerers of level 33 and above. Would you like to buy a wand of inferno for %P gold?", Topic=92 + +"snakebite","rod" -> Type=3066, Amount=1, Price=500, "This rod is only for druids of level 7 and above. Would you like to buy a snakebite rod for %P gold?", Topic=92 +"moonlight","rod" -> Type=3070, Amount=1, Price=1000, "This rod is only for druids of level 13 and above. Would you like to buy a moonlight rod for %P gold?", Topic=92 +"volcanic","rod" -> Type=3069, Amount=1, Price=5000, "This rod is only for druids of level 19 and above. Would you like to buy a volcanic rod for %P gold?", Topic=92 +"quagmire","rod" -> Type=3065, Amount=1, Price=10000, "This rod is only for druids of level 26 and above. Would you like to buy a quagmire rod for %P gold?", Topic=92 +"tempest","rod" -> Type=3067, Amount=1, Price=15000, "This rod is only for druids of level 33 and above. Would you like to buy a tempest rod for %P gold?", Topic=92 + +%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=500*%1, "This wand is only for sorcerers of level 7 and above. Would you like to buy %A wands of vortex for %P gold?", Topic=92 +%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=1000*%1, "This wand is only for sorcerers of level 13 and above. Would you like to buy %A wands of dragonbreath for %P gold?", Topic=92 +%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=5000*%1, "This wand is only for sorcerers of level 19 and above. Would you like to buy %A wands of plague for %P gold?", Topic=92 +%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=10000*%1, "This wand is only for sorcerers of level 26 and above. Would you like to buy %A wands of cosmic energy for %P gold?", Topic=92 +%1,1<%1,"wand","of","inferno" -> Type=3071, Amount=%1, Price=15000*%1, "This wand is only for sorcerers of level 33 and above. Would you like to buy %A wands of inferno for %P gold?", Topic=92 + +%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=500*%1, "This rod is only for druids of level 7 and above. Would you like to buy %A snakebite rods for %P gold?", Topic=92 +%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=1000*%1, "This rod is only for druids of level 13 and above. Would you like to buy %A moonlight rods for %P gold?", Topic=92 +%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=5000*%1, "This rod is only for druids of level 19 and above. Would you like to buy %A volcanic rods for %P gold?", Topic=92 +%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=10000*%1, "This rod is only for druids of level 26 and above. Would you like to buy %A quagmire rods for %P gold?", Topic=92 +%1,1<%1,"tempest","rod" -> Type=3067, Amount=%1, Price=15000*%1, "This rod is only for druids of level 33 and above. Would you like to buy %A tempest rods for %P gold?", Topic=92 + + +Topic=92,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=92,"yes" -> "Sorry, you don't have enough gold." +Topic=92 -> "You don't know what you're missing." \ No newline at end of file diff --git a/data/npc/gen-t-weapon-s.ndb b/data/npc/gen-t-weapon-s.ndb new file mode 100644 index 0000000..07f2de4 --- /dev/null +++ b/data/npc/gen-t-weapon-s.ndb @@ -0,0 +1,27 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-t-weapon-s.ndb: Datenbank für generischen Waffenverkauf + +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, battle axes, sickles, short swords, swords, carlin swords, two handed swords, rapiers, daggers, clubs, morning stars and sabres. What's your choice?" + +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=33 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=33 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=33 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=33 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=33 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=33 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=33 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=33 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=33 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=33 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=33 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=33 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=33 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=33 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=33 +"carlin","sword" -> Type=3283, Amount=1, Price=473, "Do you want to buy one of the excellent carlin swords for %P gold?", Topic=33 + + +Topic=33,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=33,"yes" -> "Sorry, you do not have enough gold." +Topic=33 -> "Maybe you will buy it another time." + diff --git a/data/npc/gen-xmas.ndb b/data/npc/gen-xmas.ndb new file mode 100644 index 0000000..885ee48 --- /dev/null +++ b/data/npc/gen-xmas.ndb @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gen-xmas.ndb: Datenbank für generische Weihnachtsmänner + +ADDRESS,"hello$",! -> "Merry Christmas, little %N!", Amount=Random(1,10000) +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait! I am ready for you soon!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, little %N!" + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "Ho ho ho! You don't know Santa Claus? Never mind. You may ask me for a present." +"name" -> "Sorry, I don't have time to chat. Please ask for your present." + +#verteilt orange +"present",QuestValue(217)<3,amount<1000,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3586, Amount=10,Create(Type),Idle +#verteilt candy canes +"present",QuestValue(217)<3,amount<3500,amount>999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3599, Amount=8,Create(Type),Idle +#verteilt apfel +"present",QuestValue(217)<3,amount<5000,amount>3499,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3585, Amount=15,Create(Type),Idle +#verteilt kekse +"present",QuestValue(217)<3,amount<6000,amount>4999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=3598, Amount=8,Create(Type),Idle +#verteilt schneeball +"present",QuestValue(217)<3,amount<9950,amount>5999,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=2992, Amount=5,Create(Type),Idle +#verteilt doll +"present",QuestValue(217)<3,amount<9999,amount>9949,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=2991, Amount=1,Create(Type),Idle +#verteilt teddy +"present",QuestValue(217)<3,amount=10000,! -> "Here is your present! Enjoy!",SetQuestValue(217,3),Type=2993, Amount=1,Create(Type),Idle + + +"present",QuestValue(217)=3 -> "You already got your present! Next please!", Idle + diff --git a/data/npc/gentest.ndb b/data/npc/gentest.ndb new file mode 100644 index 0000000..b255c70 --- /dev/null +++ b/data/npc/gentest.ndb @@ -0,0 +1,6 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gentest.ndb: Datenbank für den test generischer händler + +"news" -> "I am busy. Please ask the citizens for news." +"sell" -> "Visit shopkeepers to buy their fine wares." +"job" -> "I am a trader." diff --git a/data/npc/gorn.npc b/data/npc/gorn.npc new file mode 100644 index 0000000..02fcd0b --- /dev/null +++ b/data/npc/gorn.npc @@ -0,0 +1,129 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gorn.npc: Datenbank für den Händler Gorn + +Name = "Gorn" +Outfit = (129,58-68-101-95) +Home = [32377,32200,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment of all kinds. Do you need anything?" +"name" -> "I am Gorn. My goods are known all over Tibia." +"time" -> "It is exactly %T. Maybe you want to buy a watch?" + +"food" -> "If you are looking for food, go to Frodo's Hut." +"king" -> "The king supports Tibia's economy a lot." +"tibianus" -> * +"quentin" -> "He advices newcomers to buy at my store. I love that guy!" +"lynda" -> "That's a pretty one." +"harkath" -> "I hardly know him." +"army" -> "Armies are too hierarchical for my taste." +"ferumbras" -> "We had a clash or two in the old days." +"general" -> "I don't like titles." +"sam" -> "Strong as an ox, could armwrestle a minotaur, I bet." +"frodo" -> "Frodo is a jolly fellow." +"elane" -> "Elane is the leader of the paladin guild." +"paladin" -> * +"muriel" -> "You can find Muriel in the sorcerer guild." +"sorcerer" -> * +"gregor" -> "Even the strong knights need my equipment on their travels though Tibia." +"knight" -> * +"marvik" -> "These druids are nice people, you will find them in the east of the town." +"druid" -> * +"bozo" -> "Bah! Go away with this bozoguy." +"baxter" -> "Old Baxter was a rowdy, once. In our youth we shared some adventures and women." +"oswald" -> "This Oswald has not enough to work and too much time to spread rumours." +"sherry" -> "I hardly know the McRonalds." +"donald" -> * +"mcronald" -> * +"lugri" -> "Never heared that name." +"excalibug" -> "I would pay thousands of gold coins for this weapon." +"news" -> "Taxes will increase soon, so buy as much as you can right now." + +"offer" -> "My inventory is large, just have a look at the blackboards." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + +"ammunition" -> "Galuna sells them now in her own shop. Go and ask her about that." +"bow" -> * +"crossbow" -> * +"arrow" -> * +"bolt" -> * +"galuna" -> "In the past she delivered me with all the bows and arrows. She has now her own shop at the paladin guild." +"magic" -> "Magic? Ask a sorcerer or druid about that." +"fluid" -> "Find the magic shop." +"xodet" -> "He owns the magic shop here. But be aware: The prices are enormous." +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=40, "Do you want to buy a machete for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=40*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/graubart.npc b/data/npc/graubart.npc new file mode 100644 index 0000000..45c3937 --- /dev/null +++ b/data/npc/graubart.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# graubart.npc : Kapitän Graubart von der Seahawk (Fields) + +Name = "Graubart" +Outfit = (128,98-87-12-114) +Home = [32489,31622,07] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ahoi, young man %N. Looking for work on my ship?" +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Ahoi, young lady %N. Looking for work on my ship?" +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and don't forget me!" + +"bye" -> "Good bye and don't forget me!", Idle +"farewell" -> * +"work" -> "I'm sorry, but it is too dangerous nowadays. Too many storms out there. Come back in some months and we will see." +"name" -> "My name is Graubart, captain of the great Seahawk!" +"job" -> "I'm a merchant. I sail all over the world with my ship and trade with many different races!" +"races" -> "You know; elves, dwarfs, lizardmen, minotaurs and many others." +"ship" -> "Ah, my whole proud: My ship named Seahawk. We rode out so many stormy nights together. I think I couldn't live without it." +"seahawk" -> * +"trade" -> "I trade nearly everything, for example weapons, food, water, and even magic runes." +"merchant" -> "A merchant is someone who trades goods with other people and tries to make a little profit. *laughs*" +"weapons" -> "Sorry, sold out." +"food" -> "Sorry, sold out. Ask Bruno." +"water" -> "Sorry, sold out." +"magic","runes"-> "Sorry, sold out." +"bruno" -> "Bruno is one of the best sailors I know. He is nearly as good as me. *laughs loudly*" +"aneus" -> "Hmm, I don't know him very well. But he has a very nice story to tell." +"marlene" -> "Pssst. Marlene is not near right now...? You know... she is a lovely woman, but she talks too much! So I always try to keep distance from her because she can't stop talking." +} diff --git a/data/npc/gregor.npc b/data/npc/gregor.npc new file mode 100644 index 0000000..f7df62d --- /dev/null +++ b/data/npc/gregor.npc @@ -0,0 +1,84 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gregor.npc: Datenbank fuer den Ritter Gregor + +Name = "Gregor" +Outfit = (131,38-38-38-38) +Home = [32407,32202,6] +Radius = 4 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Welcome home, Knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,"hello$",! -> "Greetings, %N. What do you want?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Be careful on your journeys." + +"bye" -> "Be careful on your journeys.", Idle +"farewell" -> * +"job" -> "I am the first knight. I trained some of the greatest heroes of Tibia." +"name" -> "You are joking, eh? Of course, you know me. I am Gregor, the first knight." +"time" -> "It is time to join the Knights!" +"king" -> "Hail to our King!" +"tibianus" -> * +"quentin" -> "I will never understand this peaceful monks and priests." +"lynda" -> "Before she became a priest she won the Miss Tibia contest three times in a row." +"harkath" -> "One of Tibia's greatest warriors and strategists." +"army" -> "I teached many of the guards personally." +"ferumbras" -> "A fine game to hunt. But be careful, he cheats!" +"general" -> "General Harkath Bloodblade, a rolemodel." +"sam" -> "He has the muscles, but lacks the guts." +"gorn" -> "Always concerned with his profit. What a loss! He was adventuring with baxter in the old days." +"frodo" -> "I and my students often share a cask of beer or wine at Frodo's hut." +"elane" -> "A bow might be a fine weapon for someone not strong enough to wield a REAL weapon." +"muriel" -> "Bah, go away with these sorcerer tricks. Only cowards use tricks." +"gregor" -> "A great name, isn't it?" +"marvik" -> "Old Marvik saved life and limb of many of my boys and girls." +"bozo" -> "Some day someone will make something happen to him..." +"baxter" -> "He was an adventurer once." +"oswald" -> "What an idiot." +"sherry" -> "Peaceful farmers." +"donald" -> * +"mcronald" -> * +"lugri" -> "If he would have some guts he would fight for what he's talking about." +"excalibug" -> "Many brave warriors died on the quest to find that fabled weapon." +"news" -> "Times of war are at hand." + +"hero" -> "Of course, you heard of them. Knights are the best fighters in Tibia." +"tibia" -> "Beautiful Tibia. And with our help everyone is safe." +"knight" -> "Knights are the warriors of Tibia. Without us, no one would be safe. Every brave and strong man or woman can join us." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Knights, paladins, sorcerers, and druids." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Muriel, the sorcerer." + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Be careful on your journeys.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." +} diff --git a/data/npc/grof.npc b/data/npc/grof.npc new file mode 100644 index 0000000..06673ab --- /dev/null +++ b/data/npc/grof.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# grof.npc: Datenbank für die Stadtwache am Nordtor + +Name = "Grof, the guard" +Outfit = (131,19-19-19-19) +Home = [32373,32184,7] +Radius = 3 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/data/npc/guards-carlin.ndb b/data/npc/guards-carlin.ndb new file mode 100644 index 0000000..b1f88e4 --- /dev/null +++ b/data/npc/guards-carlin.ndb @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-carlin.ndb: Datenbank für die Stadtwachen von Carlin + +ADDRESS,"hello$",! -> "LONG LIVE THE QUEEN!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy right now!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Hrmpf!" + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"farewell" -> * +"news" -> "I am busy. Please ask the citizens for news." +"how","are","you" -> "I am healthy and vigilant." +"sell" -> "Visit Carlin's shopkeepers to buy their fine wares." +"queen" -> "Queen Eloise is our beloved sovereign!" +"leader" -> * +"job" -> "It's my duty to protect our fair city." +"army" -> "Of course, we guards are members of the army." +"guard" -> "I am a guard and proud of it." +"name" -> "It's Miss Bonecrusher to you!" +"bonecrusher" -> "The bonecrusher family has been serving in the army of Carlin for generations. My sister Bunny is the general of our army." +"army" -> "Ask my sister Bunny for details." +"castle" -> "The castle is in the northwest corner of the city." +"green","ferrets" -> "Brave warriors, indeed." +"knights","of","noodles" -> "WOOF! WOOF! Go away with theese puny king's puppys." +"secret","police" -> "Ask a higher offical about that." +"city" -> "Behave well, while in the city, or we'll get you! Do you want to know where to find a shop or a guild?" +"shop" -> "There's a smith, a provisioner, a tavern, a magic shop, and the royal post office, of course." +"guild" -> "In the city you will find the guildhouses of the Knights, the Paladins, the Druids, and the Sorcerers." +"scum" -> "We will get rid of all scum." +"barbara" -> "Fine warrioress, even if not of our family." +"fenbala" -> * +"bunny" -> "My sister is in charge of our mighty army." +"busty" -> "We are Bonecrushers. Mess with one of us, and we all will come for you!" +"bambi" -> * +"blossom" -> * +"banor" -> "Praise Banor! May the great warrior be with us!" +"graveyard" -> "Stay away from the graveyard, it's haunted!" +"crypt" -> * +"haunted" -> "Strange things happen in our graveyard, and sometimes there are .. noises." +"noise" -> "I never heared them myself, but people, one can trust, did." +"male$" -> "Bah! Who cares about males? Let them do males' work, cleaning the sewers for example." +"sewer" -> "Sewers are filthy and disgusting. We let the men take care of them." +"rebellion" -> "What a joke. The men have no guts for a rebellion." +"resistance" -> * +"tod$" -> "There once was an adventurer with this name in town, made some trouble, got kicked out. No big deal." +"ghostlands" -> "In theory the ghostlands are of limits. But we don't enforce that. Anyone stupid enough to go there will meet his deserved fate." + +"fletcher" -> "You can find the weapon and armour shops just west of the towncenter." +"smith" -> * +"weapon" -> * +"armor" -> * +"sarina" -> "Sarina is our provisioner. You can find her in the towncenter, south of the weaponshops." +"provision" -> * +"dane" -> "Dane runs the local tavern and hotel. You can find it at the southwest beach of Carlin." +"tavern" -> * +"liane" -> "Liane is a kind person. She runs the post office in the town's center." +"post" -> * +"depot" -> "We have some depots in our town. You can't miss them." +"lea$" -> "Lea is the head of the local sorcerers guild. You can find it south of the towncenter at the magic shop of Rachel." +"sorcerer" -> * +"legola" -> "Legola runs the local paladins guild, it's near the westgate." +"paladin" -> * +"padreia" -> "Padreia is the greatest druid of the continent. You find the guild of the benevolent druids in the southwest of the city." +"druid" -> * +"trisha" -> "Trisha is the leader of our knights guild. It is in the north of the town, near the gate." +"knight" -> * +"rachel" -> "Rachel sells equipment for all magic users in her shop. There is also the sorcerer guild in the second floor." +"magic" -> * +"spell" -> * + +"fuck" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) diff --git a/data/npc/guards-darama.ndb b/data/npc/guards-darama.ndb new file mode 100644 index 0000000..c16d00f --- /dev/null +++ b/data/npc/guards-darama.ndb @@ -0,0 +1,41 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-darama.ndb: Datenbank für die Stadtwachen von Darama + +ADDRESS,"hello$",! -> "Daraman's blessings!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings!" + +"bye" -> "Daraman's blessings!", Idle +"news" -> "The evil of Drefia is strong in these days." +"how","are","you"-> "I am on guard and my soul is strong." +"sell" -> "Go to the bazaar in the middle of the town." +"buy" -> * +"caliph" -> "Our leader is enlightened by Daraman, thrice praised be his name!" +"leader" -> * +"job" -> "I am a protector of the people of Darashia." +"army" -> "This information is confidential." +"guard" -> * +"daraman" -> "The ancient prophet brought our people here from a place long forgotten." +"pilgrim" -> "Darama led his people on a pilgrimage to the holy lands of Darama to escape temptations and distractions." +"necromancer" -> "It's said they fled from the corrupted continent of the thaian empire after losing some battles." +"city" -> "This city is lovely Darashia, pearl of Darama." +"darashia" -> * +"darama" -> "Darashia is the heart of Darama. In the northeast, across the Devourer, there is the dark pyramid. In the west, across the Plague Spike, are the cursed ruins of Drefia." +"plague","spike" -> "Mountain of Poison, Scorpions Rock, it's known by many names. It's to the west of Darashia." +"devourer" -> "The great desert devoured many of the first pilgrims. We learned the ways of the devourer and thus conquered it." +"pyramid" -> "The minotaurs took shelter in the ancient pyramid, which our people avoid for the dark powers that might be present there." +"drefia" -> "The ruins of Drefia in the far west are the hideout of the vile necromancer cult that once corrupted this city." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) diff --git a/data/npc/guards-thais.ndb b/data/npc/guards-thais.ndb new file mode 100644 index 0000000..46e80d8 --- /dev/null +++ b/data/npc/guards-thais.ndb @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-thais.ndb: Datenbank für die Stadtwachen von Thais + +ADDRESS,"hello$",! -> "LONG LIVE THE KING!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "I am busy right now!." +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH,! -> "Hrmpf!" + +"bye" -> "LONG LIVE THE KING!", Idle +"farewell" -> * +"news" -> "I am busy. Please ask the citizens for news." +"how","are","you"-> "I am healthy and vigilant." +"sell" -> "Visit Tibia's shopkeepers to buy their fine wares." +"tibianus" -> "Tibianus III is our beloved king!" +"king" -> * +"leader" -> * +"job" -> "It's my duty to protect the city." +"army" -> "Of course we guards are members of the army." +"guard" -> "I am a guard and proud of it." +"battlegroups" -> "Ask higher officials about that, please." +"castle" -> "The castle is at the west of the city." +"dogs", "of", "war" -> "Brave warriors, indeed." +"knights", "of", "noodles" -> "Every guard dreams of becoming one of them one day." +"red", "guard" -> "We of the red guard are the special forces." +"secret", "police" -> "Ask a higher offical about that." +"silver", "guard" -> "Only the best of the best serve as silver guards." +"city" -> "Behave while in the city or we get you! Do you want to know where to find a shop or a guild?" +"shop" -> "There's a smith, a provisioner, and a tavern." +"guild" -> "In the city you will find the guildhouses of the knights, paladins, druids, and sorcerers." +"scum" -> "We will get rid of all scum." +"stutch" -> "He is a soldier in the silver guard." +"harsky" -> * +"baxter" -> "He is a role model for us." +"bozo" -> "The royal jester." +"harkath$" -> "The royal general. A warrior worth Banor's blessings." +"bloodblade$" -> * +"general$" -> * +"banor" -> "Praise Banor! May the great warrior be with us!" +"sam" -> "Sam is our blacksmith. You'll find him north of the main crossroads. His shop is to the left." +"smith" -> * +"weapon" -> * +"armor" -> * +"gorn" -> "Gorn is our provisioner. You'll find him north of the main crossroads. His shop is to the right." +"provision" -> * +"frodo" -> "Frodo runs the local tavern. You'll find it at the main crossroads to the north-west." +"tavern" -> * +"benjamin" -> "Benjamin was a brave fighter. He runs the post office in the west of the city." +"post" -> * +"depot" -> "The depot is at the post office in the west of the city." +"muriel" -> "Muriel is the head of the local sorcerers' guild. You'll find it in the south-west of the city" +"sorcerer" -> * +"elane" -> "Elane is responsible for the local paladins' guild. It's in the west of the town, directly south of the post office." +"paladin" -> * +"marvik" -> "Marvik is the great druid of the local guild. You'll find him by climbing up the citywalls at the east." +"druid" -> * +"gregor" -> "The high knight of the knights' guild. It is in north-east of the town." +"knight" -> * + +"fuck" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(5,1), EffectOpp(5), EffectMe(8) diff --git a/data/npc/guards-venore.ndb b/data/npc/guards-venore.ndb new file mode 100644 index 0000000..317e1b3 --- /dev/null +++ b/data/npc/guards-venore.ndb @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# guards-venore.ndb: Datenbank für die Stadtwachen von Venore + +ADDRESS,"hello$",! -> "LONG LIVE THE KING!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$" -> "Not now, I am busy!" +BUSY,"hi$" -> * +BUSY,! -> NOP +VANISH -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "Look for the Hard Rock Tavern to learn the latest news." +"how","are","you" -> "That's classified information!" +"sell" -> "Ask the merchants of the city. There are more then enough." +"buy" -> * +"tibianus" -> "Tibianus III is our beloved king!" +"king" -> * +"leader" -> * +"job" -> "I am a protector of the people of Venore." +"army" -> "This information is confidential." +"guard" -> * +"army" -> "All guards are members of the army." +"guard" -> "I am a guard and proud of it." +"battlegroups" -> "I doubt you have the security clearance to ask that." +"castle" -> "The castle is in Thais, the crown of the kingdom." +"dogs","of","war" -> "They are our rolemodells." +"knights","of","noodles" -> "Every guard dreams of becoming one of them one day." +"red","guard" -> "We of the red guard are the special forces and town guards." +"secret","police" -> "This information is confidential." +"silver","guard" -> "Only the best of the best serve as silver guards." +"city" -> "This city, a member of the Thaian kingdom, is under the protection of the Thaian army." + +"venore" -> "The harbour is to the north, the weapon market in the south, the general market to the west, and the bank to the east. You will find other shops and the Hard Rock Tavern in the center." +"swampel" -> "Those elves hide in the swamps and are trying to kill all humans in this area." +"amazon" -> "They are the best example for the results of the Carlin madness." +"swamptroll" -> "This hideous creatures are even more ugly than the normal trolls. They are treacherous and use several poisons." +"swamp" -> "The swamp is a dangerous place and full of monsters, not to mention all those swampelves living at shadowthorn, amazons, and swamptrolls." +"monsters" -> "The swamp is full of nasty snakes and there's a dragon breeding ground somewhere in the swamps." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) diff --git a/data/npc/gundralph.npc b/data/npc/gundralph.npc new file mode 100644 index 0000000..a3e7556 --- /dev/null +++ b/data/npc/gundralph.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gundralph.npc: Datenbank für den Zauberlehrer Gundralph + +Name = "Gundralph" +Outfit = (9,0-0-0-0) +Home = [33268,31849,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, be welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye." + +"bye" -> "Goodbye", Idle +"job" -> "I am a teacher for some powerful Spells." +"name" -> "They call me Gundralph." +"time" -> "Let me see, it's %T." +"king" -> "Unfortunately, I never met King Tibianus III in person." +"tibianus" -> * +"army" -> "They live in the castle to the west." +"ferumbras" -> "How low can a sorceror sink." +"excalibug" -> "An awesome weapon if it exists." +"thais" -> "I see Thais as a lost course." +"tibia" -> "The world is so big and we have only so little time to travel." +"carlin" -> "Carlin is a fine place for druids." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I have heard nothing of intrest lately, sorry." +"rumors" -> * + +"spellbook" -> "Please ask the stationer in the west tower for that." +"spell" -> "I have 'Ultimate Light', 'Soulfire', 'Magic Wall', 'Cancel Invisibility', and 'Undead Legion'. Are you interested?" + +"ultimate","light",Sorcerer -> String="Ultimate Light", Price=1600, "Do you want to learn the spell 'Ultimate Light' for %P gold?", Topic=1 +"ultimate","light",Druid -> * +"ultimate","light" -> "I'm sorry, but this spell is only for druids and sorcerers." +"soulfire",Sorcerer -> String="Soulfire", Price=1800, "Do you want to learn the spell 'Soulfire' for %P gold?", Topic=1 +"soulfire",Druid -> * +"soulfire" -> "I'm sorry, but this spell is only for druids and sorcerers." +"magic","wall",Sorcerer -> String="Magic Wall", Price=2100, "Do you want to learn the spell 'Magic Wall' for %P gold?", Topic=1 +"magic","wall" -> "I'm sorry, but this spell is only for sorcerers." +"undead","legion",Druid -> String="Undead Legion", Price=2000, "Do you want to learn the spell 'Undead Legion' for %P gold?", Topic=1 +"undead","legion" -> "I'm sorry, but this spell is only for druids." +"cancel","invisibility",Sorcerer -> String="Cancel Invisibility", Price=1600, "Do you want to learn the spell 'Cancel Invisibility' for %P gold?", Topic=1 +"cancel","invisibility" -> "I'm sorry, but this spell is only for sorcerers." + +Topic=1,"yes",SpellKnown(String)=1 -> "Hmm, you already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "Hmm, you need to advance to level %A to learn this spell." +Topic=1,"yes",CountMoney "Hmm, you don't have enough money to pay my service." +Topic=1,"yes" -> "Voila, from now on you can cast this spell. Use your knowledge wisely.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/gurbasch.npc b/data/npc/gurbasch.npc new file mode 100644 index 0000000..22d5ba7 --- /dev/null +++ b/data/npc/gurbasch.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# gurbasch.npc: Datenbank für den Kapitän Gurbasch + +Name = "Gurbasch" +Outfit = (66,0-0-0-0) +Home = [33313,31989,15] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Don't hurry.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "No patience, these brats!" + +"bye" -> "Until next time.", Idle +"farewell" -> * +"fare","thee","well" -> "Thou art truly in possession of fine manners. Blessings upon you!", EffectOpp(13) +"job" -> "As should be quite obvious, I am operating a steamship." +"work" -> * +"name" -> "I am Gurbasch Firejuggler, son of the machine, of the Molten Rock." +"tibia" -> "Tibia? Just don't ask." +"ship" -> "It is indeed something we dwarfs may be proud of: a ship operating by steam power." +"steamship" -> * +"captain" -> "Captain" +"technomancer" -> "A technomancer wields power over incredible machines, as his knowledge is his magic." +"inventors" -> "You know, elves may be intelligent, but they are too lazy to invent. Really." +"inventions" -> * +"sell" -> "I am not a vendor." +"buy" -> * +"thais" -> "How do you expect me to go there? Fly? Hm, wait... no, sorry." +"ab'dendriel" -> * +"carlin" -> * +"venore" -> * +"senja" -> * +"folda" -> * +"vega" -> * +"ice","islands" -> * +"darashia" -> * +"darama" -> * +"cormaya" -> "Hey, we ARE at Cormaya! Must be the cavemadness..." +"beer" -> "Ah, you got some? Nah, beer only tastes fine in Kazordoon. If you have brought it from there, it tastes foul now, I guess." +"dwarf" -> "We are an old and proud race, although we posess the best inventions." +"brodrosch" -> "He is my brother working the Kazordoon steamship." +"elves" -> "Have one elf onboard a ship, and you are doomed." +"elf" -> * + +"kazordoon" -> Price=160, "Do you want to go to Kazordoon? And try the beer there? %P gold?", Topic=1 +"passage" -> * + +"kazordoon",QuestValue(250)>2 -> Price=150, "Do you want to go to Kazordoon? And try the beer there? %P gold?", Topic=1 +"passage",QuestValue(250)>2 -> * + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",Premium,CountMoney>=Price -> "Full steam ahead!", DeleteMoney, Idle, EffectOpp(3), Teleport(32658,31957,15), EffectOpp(3) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to travel onboard our ships." +Topic=1,"yes" -> "You don't have enough money." +} diff --git a/data/npc/habdel.npc b/data/npc/habdel.npc new file mode 100644 index 0000000..5b1a3cb --- /dev/null +++ b/data/npc/habdel.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Habdel.npc: Datenbank für den Waffenhändler Habdel + +Name = "Habdel" +Outfit = (129,95-2-0-97) +Home = [33225,32434,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! See the fine weapons I sell." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will have finished soon %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and please come back soon.", Idle + +"bye" -> "Good bye. Come back soon.", Idle +"job" -> "I sell weapons that are as lethal as the bite of the desertlion and as quick as the sandwasp." +"shop" -> * +"name" -> "My name is Habdel Ibn Haqui." +"time" -> "Don't worry, there is enough time left to finish our deal." +"help" -> "I sell and buy weapons. Just ask what you need or tell me what you offer." +"monster" -> "With my weapons you have to fear the monsters no longer and you will brave any danger or dungeon!" +"dungeon" -> * +"drefia" -> "Even the undead will fall a second time for the weapons you buy from me." +"thanks" -> "You are welcome." +"thank","you" -> * +"do","you","sell" -> "Which of my powerful weapons do you need?" +"do","you","have" -> * +"offer" -> "My offers are light and heavy weapons." +"weapon" -> * +"light" -> "I have clubs, daggers, spears, swords, maces, rapiers, morning stars, and sabres. What's your choice?" +"light","weapon" -> * +"heavy" -> "I have the best two handed swords in Tibia. I also sell battle hammers and battle axes. What's your choice?" +"heavy","weapon" -> * +"armor" -> "I sell only weapons. For armor, ask Azil in the other shop." +"shield" -> * +"helmet" -> * +"trousers" -> * +"legs" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "Do you want to buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "Do you want to buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "Do you want to buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "Do you want to sell a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "Do you want to sell a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "Do you want to sell a war hammer for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "Do you want to sell %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Do you want to sell %A clubs for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Do you want to sell %A spears for %P gold", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "Do you want to sell %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "Do you want to sell %A war hammers for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Fine. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/hagor.npc b/data/npc/hagor.npc new file mode 100644 index 0000000..b4edda2 --- /dev/null +++ b/data/npc/hagor.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hagor.npc: Datenbank für den alten Abenteurer Hagor (Desert) + +Name = "Hagor" +Outfit = (129,19-58-105-94) +Home = [32654,32151,10] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, adventurer %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"name" -> "My name is Hagor, the old hunter." +"job" -> "I travel through the lands of Tibia and now Jakundaf Desert since years." +"tibia" -> "The gods made this world full of fascinating secrets and I will search them till my end." +"thais" -> "Thais... It's a big city in Middle-Tibia. Lots of people live there." +"carlin" -> "Is this the city to the north? I heard rumours about it." +"king" -> "Tibianus is our king. To be honest, I didn't hear much of him till now." +"weapon" -> "Weapons? Do you have some? You better know how to use them!" +"help" -> "I'd really like to help you. Could you specify your request?" +"time" -> "Time has no meaning to me." +"sword" -> "Do you mean any sword in particular? Or just any sword?" +"desert" -> "Yes, it's big, isn't it?" +"excalibug" -> "I heared rumours that there is a sword called so. I don't know if it exists." +"fight" -> "Fighting is an art. Know it and you will be strong, ignore it and you will die soon!" +"guild" -> "There are many different guilds in Tibia. They come and go, come and go..." +"god" -> "There are a lot of gods we believe in. Maybe you should check out different books to find out something about them." +"way" -> "Which way are you looking for in particular?" +"door" -> "Which door are you talking about? If it is locked, maybe you should try to find a key for it!" +"secret" -> "There are many secrets. But I fear, I can't tell you much about them. They are also secret to me..." +"treasure" -> "Someone told me - I can't remember who it was - that there was a treasure hidden nearby." +"book" -> "Yes, I really can recommend reading books. It might help you find what you are looking for!" +"gharonk" -> "Gharonk is a very old language, only spoken by a few people. It's not a very complex language, but that does not mean that it is easy to understand!" +"offer" -> "Go ahead. I don't sell or buy anything!" +"exit" -> "Yes, there is an exit for these dungeons. Just find the teleporter." +"library" -> "There is a library in here, right. I assume you are talking about this library. It's locked, as far as I know. But somewhere there has to be a key... maybe the librarian knows more?" +"roll" -> "Oh, yes, I love them!" + +"morrin" -> "Ah, I remember that man. We made a deal, guess about what.", Topic=1 +Topic=1,"key" -> "Right! We can make the same deal if you give a fresh delicious roll. Do you have any?", Topic=2 +Topic=2,"yes",Count(3601)>=1 -> "Oh, fine! Here you are.", Amount=1, Delete(3601), Data=4022, Create(2969) +Topic=2,"yes" -> "Hey, you do not have one!" +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/hairycles.npc b/data/npc/hairycles.npc new file mode 100644 index 0000000..5f39172 --- /dev/null +++ b/data/npc/hairycles.npc @@ -0,0 +1,185 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hairycles.npc: Datenbank für den Affen Hairycles + +Name = "Hairycles" +Outfit = (117,0-0-0-0) +Home = [32826,32574,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(293)>11,! ->"Be greeted, friend of the ape people. If you want to trade, just ask for my offers. If you are injured, ask for healing." +ADDRESS,"hi$",QuestValue(293)>11,! -> * + +ADDRESS,"hello$",! -> "Oh! Hello! Hello! Did not notice!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait! Wait! Time I no have!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "Me fine, me fine." +"advice" -> "You stay away from other apes. We not like foreigners. Especially with so little hair." +"job" -> "Me great wizard. Me great doctor. Me know many plants. Me old and me have seen many things." +"name" -> "Me is Hairycles." +"time" -> "You look to suns or moon and time you know." +"help" -> "Me not help you can. Other apes would get mad at me." +"jungle" -> "Jungle is dangerous. Jungle also provides us food. Take care when in jungle and safe you be." +"city" -> "City now our is. Chasing away evil snakemen." +"snakemen" -> "Evil snakemen mean to apes and making them work and holding them captive since apes can think. But then Spartaky came." +"spartaky" -> "He great ape was. He fled to jungle, taught other apes of snakemen secrets. Came back with other apes and together we chased snake people away. Made city our home." +"port","hope" -> "Strange hairless ape people there live. We go and get funny things from strange people." +"ape","people" -> "We be kongra, sibang and merlkin. Strange hairless ape people live in city called Port Hope." +"kongra" -> "Kongra verry strong. Kongra verry angry verry fast. Take care when kongra comes. Better climb on highest tree." +"sibang" -> "Sibang verry fast and funny. Sibang good gather food. Sibang know jungle well." +"merlkin" -> "Merlkin we are. Merlkin verry wise, merlkin learn many things quick. Teach other apes things a lot. Making heal and making magic." +"magic" -> "We see many things and learning quick. Merlkin magic learn quick, quick. We just watch and learn. Sometimes we try and learn." +"weapon" -> "We weapons not need much. Take what is around we do. Tools we more need." +"tools" -> "Lot of tools snakemen left when run away. But tools go break. New tools we get where we find. Like taking banana." +"tibia" -> "Me know Tibia is all we see." + +"heal" -> "You look for food and rest." +"heal$",QuestValue(293)>11,Burning>0 -> "You are burning. Me will help you.", Burning(0,0), EffectOpp(15) +"heal$",QuestValue(293)>11,Poison>0 -> "You are poisoned. Me will help you.", Poison(0,0), EffectOpp(14) +"heal$",QuestValue(293)>11,HP<50 -> "You are looking really bad. Let Hairycles heal wounds.", HP=50, EffectOpp(13) + + +"offers",QuestValue(293)<11 -> "Me nothing have to offer you now. Perhaps ask later, when we know better." + +"banana" -> "Banana is good. Is magic fruit. Banana makes happy. Banana means life. Banana is everything." +"language" -> "Strange hairless ape in loincloth came here. Zantar his name was. Brought many banana. We him liked. He here lived. Taught Hairycles funny language." +# "mission" -> "Perhaps help you can. But not now. Later you ask again. Me fire light when help is needed." + +"mission" ,QuestValue(293)<1 -> "These are dire times for our people. Problems plenty are in this times. But me people not grant trust easy. Are you willing to prove you friend of ape people?", topic=1 +topic=1,"no" -> "Hairycles sad is now. But perhaps you will change mind one day." +topic=1,"yes" -> "To become friend of ape people a long and difficult way is. We do not trust easy but help is needed. Will you listen to story of Hairycles?", topic=2 + +"no", topic=2 -> "Hairycles thought better of you." +"yes", topic=2 -> "So listen, little ape was struck by plague. Hairycles not does know what plague it is. That is strange. Hairycles should know. But Hairycles learnt lots and lots ...", "Me sure to make cure so strong to drive away all plague. But to create great cure me need powerful components ...", "Me need whisper moss. Whisper moss growing south of human settlement is. Problem is, evil little dworcs harvest all whisper moss immediately ...", "Me know they hoard some in their underground lair. My people raided dworcs often before humans came. So we know the moss is hidden in east of upper level of dworc lair ...", "You go there and take good moss from evil dworcs. Talk with me about mission when having moss.",SetQuestValue(293,1) + +topic=2 -> "Uh?" + +"mission",QuestValue(293)=1, QuestValue(294)=0 -> "Please hurry. Bring me whisper moss from dworc lair. Make sure it is from dworc lair! Take it yourself only! If you need to hear background of all again, ask Hairycles for background." +"background",QuestValue(293)=1 -> "So listen, little ape was struck by plague. Hairycles not does know what plague it is. That is strange. Hairycles should know. But Hairycles learnt lots and lots ...", "Me sure to make cure so strong to drive away all plague. But to create great cure me need powerful components ...", "Me need whisper moss. Whisper moss growing south of human settlement is. Problem is, evil little dworcs harvest all whisper moss immediately ...", "Me know they hoard some in their underground lair. My people raided dworcs often before humans came. So we know the moss is hidden in east of upper level of dworc lair ...", "You go there and take good moss from evil dworcs. Talk with me about mission when having moss." + +"mission",QuestValue(293)=1, QuestValue(294)=1 -> Type=4827,Amount=1,"Oh, you brought me whisper moss? Good hairless ape you are! Can me take it?",topic=3 + +"no", topic=3 -> "Strange being you are! Our people need help!", idle +"yes", topic=3,Count(Type) "Stupid, you no have the moss me need. Go get it. It's somewhere in dworc lair. If you lost it, they might restocked it meanwhile. If you need to hear background of all again, ask Hairycles for background.",SetQuestValue(294,0) + +"yes",Count(Type)>=Amount, topic=3 -> "Ah yes! That's it. Thank you for bringing mighty whisper moss to Hairycles. It will help but still much is to be done. Just ask for other mission if you ready.",Delete(Type) ,SetQuestValue(293,2) + +"mission",QuestValue(293)=2 -> "Whisper moss strong is, but me need liquid that humans have to make it work ...", "Our raiders brought it from human settlement, it's called cough syrup. Go ask healer there for it.",SetQuestValue(293,3) + + +"mission",QuestValue(293)=3 -> Type=4828,Amount=1,"You brought me that cough syrup from human healer me asked for?", topic=4 + +"no", topic=4 -> "Please hurry, urgent it is!" +"yes", topic=4,Count(Type) "No no, not right syrup you have. Go get other, get right health syrup." +"yes", topic=4,Count(Type)>=Amount -> "You so good! Brought syrup to me! Thank you, will prepare cure now. Just ask for mission if you want help again.",Delete(Type),SetQuestValue(293,4) + +"mission",QuestValue(293)=4 -> "Little ape should be healthy soon. Me so happy is. Thank you again! But me suspect we in more trouble than we thought. Will you help us again?", topic=5 + +"no", topic=5 -> "Me sad. Me expected better from you!", idle +"yes", topic=5 -> "So listen, please. Plague was not ordinary plague. That's why Hairycles could not heal at first. It is new curse of evil lizard people ...", "I think curse on little one was only a try. We have to be prepared for big strike ...", "Me need papers of lizard magician! For sure you find it in his hut in their dwelling. It's south east of jungle. Go look there please! Are you willing to go?", topic=6 +"no", topic=6 -> "Me sad. Me expected better from you!", idle +"yes", topic=6 -> "Good thing that is! Report about your mission when have scroll.",SetQuestValue(293,5) +"mission",QuestValue(293)=5 -> Type=4831,Amount=1,"You got scroll from lizard village in south east?", topic=7 + +"no", topic= 7 -> "That's bad news. If you lost it, only way to get other is to kill holy serpents. But you can't go there so you must ask adventurers who can." + +#### Einschub, notwendige Erklärung +"holy", "serpent" -> "Ugly beasts that are holy to lizard people. Only found in ancient temple under Banuta. But me can not allow you to go there." + +"yes", topic= 7,QuestValue(295)=0,Count(Type) "No! That not scroll me looking for. Silly hairless ape you are. Go to village of lizards and get it there on your own!" +"yes", topic= 7,QuestValue(295)=1,Count(Type) "Oh, you seem to have lost scroll? That's bad news. If you lost it, only way to get other is to kill holy serpents. But you can't go there so you must ask adventurers who can." +"yes", topic= 7,QuestValue(295)=1,Count(Type)>=Amount ->"You brought scroll with lizard text? Good! I will see what text tells me! Come back when ready for other mission.",Delete(Type),SetQuestValue(293,6), Idle + +"mission",QuestValue(293)=6 -> "Ah yes that scroll. Sadly me not could read it yet. But the holy banana me insight gave! In dreams Hairycles saw where to find solution ...", "Me saw a stone with lizard signs and other signs at once. If you read signs and tell Hairycles, me will know how to read signs ...", "You go east to big desert. In desert there city. East of city under sand hidden tomb is. You will have to dig until you find it, so take shovel ...", "Go down in tomb until come to big level and then go down another. There you find a stone with signs between two huge red stones ...", "Read it and return to me. Are you up to that challenge?", topic=8 + +"no", topic=8 -> "Me sad. Me expected better from you!", idle +"yes", topic=8 -> "Good thing that is! Report about mission when you have read those signs.",SetQuestValue(293,7) + +"mission",QuestValue(293)=7, QuestValue(296)=0 -> "You still don't know signs on stone, go and look for it in tomb east in desert." +"mission",QuestValue(293)=7, QuestValue(296)=1 -> "Ah yes, you read the signs in tomb? Good! May me look into your mind to see what you saw?", topic=9 + +"no", topic=9 -> "Me need to see it in your mind, other there is no way to proceed." +"yes", topic=9 -> EffectOpp(13),"Oh, so clear is all now! Easy it will be to read the signs now! Soon we will know what to do! Thank you again! Ask for mission if you feel ready.",SetQuestValue(293,8), idle + + +"mission",QuestValue(293)=8 -> "So much there is to do for Hairycles to prepare charm that will protect all ape people ...", "You can help more. To create charm of life me need mighty token of life! Best is egg of a regenerating beast as a hydra is ...", "Bring me egg of hydra please. You may fight it in lair of Hydra at little lake south east of our lovely city Banuta! You think you can do?", topic=10 + +"no", topic=10 -> "Me sad. Me expected better from you!", idle +"yes", topic=10 -> "You brave hairless ape! Get me hydra egg. If you lose egg, you probably have to fight many, many hydras to get another.",SetQuestValue(293,9) + +"mission",QuestValue(293)=9 -> Type=4839,Amount=1,"You bring Hairycles egg of hydra?", topic=11 +"no", topic=11 -> "Please hurry. Hairycles not knows when evil lizards strike again." +"yes", topic=11,Count(Type) "You not have egg of hydra. Please get one!" +"yes", topic=11,Count(Type)>=Amount -> "Ah, the egg! Mighty warrior you be! Thank you. Hairycles will put it at safe place immediately.",Delete(Type),SetQuestValue(293,10), idle + +"mission",QuestValue(293)=10 -> "Last ingredient for charm of life is thing to lure magic. Only thing me know like that is mushroom called witches' cap. Me was told it be found in isle called Fibula, where humans live ...", "Hidden under Fibula is a secret dungeon. There you will find witches' cap. Are you willing to go there for good ape people?", topic=12 + +"no", topic=12 -> "Me sad. Me expected better from you!", idle +"yes", topic=12 -> "Long journey it will take, good luck to you.",SetQuestValue(293,11) + +"mission",QuestValue(293)=11 -> Type=4829,Amount=1,"You brought Hairycles witches' cap from Fibula?", topic=18 +"no", topic=18 -> "Please try to find me a witches' cap on Fibula." +"yes", topic=18,Count(Type) "Not right mushroom you have. Find me a witches' cap on Fibula!" +"Yes", topic=18,Count(Type)>=Amount -> "Incredible, you brought a witches' cap! Now me can prepare mighty charm of life. Yet still other missions will await you, friend.",SetQuestValue(293,12),Delete(Type) +####(begrüßung ändert sich nun) + + +"mission",QuestValue(293)=12 -> "Mighty life charm is protecting us now! But my people are still in danger. Danger from within ...", "Some of my people try to mimic lizards to become strong. Like lizards did before, this cult drinks strange fluid that lizards left when fled ...", "Under the city still the underground temple of lizards is. There you find casks with red fluid. Take crowbar and destroy three of them to stop this madness. Are you willing to do that?", topic=13 + +"no", topic=13 -> "Me sad. Please reconsider." +"yes", topic=13 -> "Hairycles sure you will make it. Good luck, friend.", SetQuestValue(293,13) + +"mission",QuestValue(293)=13,QuestValue(297)<3 -> "Please destroy three casks in the complex beneath Banuta, so my people will come to senses again." +"mission",QuestValue(293)=13,QuestValue(297)>2 -> "You do please Hairycles again, friend. Me hope madness will not spread further now. Perhaps you are ready for other mission.", SetQuestValue(293,14) + +"mission",QuestValue(293)=14 -> "Now that the false cult was stopped, we need to strengthen the spirit of my people. We need a symbol of our faith that ape people can see and touch ...", "Since you have proven a friend of the ape people I will grant you permission to enter the forbidden land ...", "To enter the forbidden land in the north-east of the jungle, look for a cave in the mountains east of it. There you will find the blind prophet ...", "Tell him Hairycles you sent and he will grant you entrance ...", "Forbidden land is home of Bong. Holy giant ape big as mountain. Don't annoy him in any way but look for a hair of holy ape ...", "You might find at places he has been, should be easy to see them since Bong is big ...", "Return a hair of the holy ape to me. Will you do this for Hairycles?", topic=14 + +"no", topic=14 -> "Me sad. Please reconsider." +"yes", topic=14 -> "Hairycles proud of you. Go and find holy hair. Good luck, friend.", SetQuestValue(293,15) + +"mission",QuestValue(293)=15,QuestValue(298)=0 -> "Get a hair of holy ape from forbidden land in east. Speak with blind prophet in cave." +"mission",QuestValue(293)=15,QuestValue(298)=1 -> Type=4832,Amount=1,"You brought hair of holy ape?", topic=15 + +"no", topic=15 -> "Go to forbidden land in east to find hair." +"yes", topic=15,Count(Type) "You no have hair. You lost it? Go and look again.", SetQuestValue(298,0) +"yes", topic=15,Count(Type)>=Amount -> "Incredible! You got a hair of holy Bong! This will raise the spirit of my people. You are truly a friend. But one last mission awaits you.",SetQuestValue(293,16),Delete(Type) + +"mission",QuestValue(293)=16 -> "You have proven yourself a friend, me will grant you permission to enter the deepest catacombs under Banuta which we have sealed in the past ...", "Me still can sense the evil presence there. We did not dare to go deeper and fight creatures of evil there ...", "You may go there, fight the evil and find the monument of the serpent god and destroy it with hammer me give to you ...", "Only then my people will be safe. Please tell Hairycles, will you go there?", topic=16 + +"no", topic=16 -> "Me sad. Please reconsider." +"yes", topic=16 -> "Hairycles sure you will make it. Just use hammer on all that looks like snake or lizard. Tell Hairycles if you succeed with mission.", Create(4835), SetQuestValue(293,17) + +"mission",QuestValue(293)=17,QuestValue(299)=0 -> "Me know its much me asked for but go into the deepest catacombs under Banuta and destroy the monument of the serpent god." +"mission",QuestValue(293)=17,QuestValue(299)=1 -> "Finally my people are safe! You have done incredible good for ape people and one day even me brethren will recognise that ...", "I wish I could speak for all when me call you true friend but my people need time to get accustomed to change ...", "Let us hope one day whole Banuta will greet you as a friend. Perhaps you want to check me offers for special friends.",SetQuestValue(293,18) +"mission",QuestValue(293)=18 -> "No more missions await you right now, friend. Perhaps you want to check me offers for special friends." + +### ACHTUNG TOPIC 18 OBEN VERWENDET +"offer",QuestValue(293)>11,QuestValue(293)<17 -> "Me offer tasty bananas." +"offer",QuestValue(293)>17 -> "Me offer tasty bananas. Me also sell statues of holy apes of wisdom. Statue of no talking, statue of no hearing, statue of no seeing." +"furniture",QuestValue(293)>17 -> * +"goods",QuestValue(293)>17 -> * +"do","you","sell",QuestValue(293)>17 -> * +"do","you","have",QuestValue(293)>17 -> * + +"statue",QuestValue(293)>17 -> "Me sell statues of holy apes of wisdom. Statue of no speaking, statue of no hearing, statue of no seeing." +"speaking",QuestValue(293)>17 -> Type=5088, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"seeing",QuestValue(293)>17 -> Type=5086, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"hearing",QuestValue(293)>17 -> Type=5087, Amount=1, Price=65, "You want buy this holy statue for %P gold?", Topic=81 +"banana",QuestValue(293)>11 -> Type=3587, Amount=1, Price=2, "You want buy this banana for %P gold?", Topic=81 +%1,1<%1,"banana",QuestValue(293)>11 -> Type=3587, Amount=%1, Price=%1*2, "You want buy %A bananas for %P gold?", Topic=81 + + +Topic=81,"yes",CountMoney>=Price -> "Here is what you want.", DeleteMoney, Create(Type) +Topic=81,"yes" -> "Me sorry, you no money." +Topic=81 -> "As you whish, but no better in whole jungle you will find." + + +} \ No newline at end of file diff --git a/data/npc/halif.npc b/data/npc/halif.npc new file mode 100644 index 0000000..2c3a619 --- /dev/null +++ b/data/npc/halif.npc @@ -0,0 +1,95 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Halif.npc: Datenbank für den Händler Halif + +Name = "Halif" +Outfit = (128,95-2-10-131) +Home = [33217,32423,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, you child of wealth and generousity. What wise decision to buy my wares." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, please could you wait a moment until I finish this deal.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, may Daraman bless your travels." + +"bye" -> "Good bye, may Daraman bless your travels.", Idle +"job" -> "Oh, I guess your cleverness already made the profession of the humble equipment tradesman obvious to you." +"light" -> "I sell torches, candlesticks, candelabras, and oil, o seeker of enlightment." +"name" -> "I am Halif Ibn Onor, known as Halif the honest." +"time" -> "I would love to tell you the time, but I can not make the watchmaker's kids starve as a gazelle in the heart of the desert." +"food" -> "I am deeply sorry but you have to look for that elsewhere." + +"equipment" -> "I sell shovels, picks, scythes, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, watches, fishing rods and sixpacks of worms. Of course, I sell lightsources, too." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2858, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2866, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 +"water","hose" -> Type=2901, Data=1, Amount=1, Price=40, "Do you want to buy a water hose for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2858, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2866, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Data=1, Amount=%1, Price=40*%1, "Do you want to buy %A water hoses for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=3 + +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "I hope it will serve you well, my prized customer.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "My twelve starving children don't allow me to sell it for less, o grandmaster of haggling." +Topic=1 -> "What a pity." + +Topic=3,"yes",Count(Type)>=Amount -> "I hardly can explain my wife I gave you that much money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you own none." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=3 -> "Maybe next time." +} diff --git a/data/npc/hanna.npc b/data/npc/hanna.npc new file mode 100644 index 0000000..83e4c00 --- /dev/null +++ b/data/npc/hanna.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hanna.npc: Datenbank für die Juwelierin Hanna + +Name = "Hanna" +Outfit = (136,113-65-0-96) +Home = [32407,32219,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a jeweler. Maybe you want to have a look at my wonderful offers." +"name" -> "I am Hanna." +"time" -> "Currently it is %T." + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and 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." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/hardek.npc b/data/npc/hardek.npc new file mode 100644 index 0000000..7d61bcb --- /dev/null +++ b/data/npc/hardek.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Hardek.npc: Datenbank für den Aufkäufer Hardek + +Name = "Hardek" +Outfit = (129,96-86-63-115) +Home = [32272,32339,7] +Radius = 20 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need my services?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N, I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Visit me whenever you want to sell something." + +"bye" -> "Good bye. Visit me whenever you want to sell something.", Idle +"farewell" -> * +"job" -> "I am buying some weapons and armors." +"forestaller" -> * +"name" -> "I am Hardek, the forestaller." +"time" -> "It is %T." +"help" -> "I buy stuff. If you want to sell something, offer it to me, and we'll see if it catches my interest." +"thanks" -> "You are welcome." +"thank","you" -> * + +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=31, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","guardian","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell a guardian shield for %P gold?", Topic=2 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell a dragon shield for %P gold?", Topic=2 + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell a coat for %P gold?", Topic=2 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell a jacket for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell a knight armor for %P gold?", Topic=2 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500, "Do you want to sell a golden armor for %P gold?", Topic=2 + +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "Do you want to sell a devil helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "Do you want to sell a warrior helmet for %P gold?", Topic=2 + +"sell","leather","legs" -> Type=3559, Amount=1, Price=1, "Do you want to sell leather legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "Do you want to sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "Do you want to sell knight legs for %P gold?", Topic=2 + +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "Do you want to sell a longsword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000, "Do you want to sell a fire sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 + +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=31*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell %A guardians shields for %P gold?", Topic=2 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=2 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell %A coats for %P gold?", Topic=2 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell %A jackets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell %A knight armors for %P gold?", Topic=2 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1, "Do you want to sell %A golden armors for %P gold?", Topic=2 + +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "Do you want to sell %A devil helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "Do you want to sell %A warrior helmets for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=1*%1, "Do you want to sell %A leather legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "Do you want to sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "Do you want to sell %A knight legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "Do you want to sell %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1, "Do you want to sell %A fireswords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/haroun.npc b/data/npc/haroun.npc new file mode 100644 index 0000000..7ff69ba --- /dev/null +++ b/data/npc/haroun.npc @@ -0,0 +1,176 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# haroun.npc: Datenbank für den Maridhändler Haroun (Magische Gegenstände, Marid) + +Name = "Haroun" +Outfit = (80,0-0-0-0) +Home = [33108,32525,4] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "Be greeted, human %N. How can a humble djinn be of service?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Please wait, human %N. I'll be with you in a minute.", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Farewell! May the serene light of the enlightened one rest shine on your travels.", Idle +"farewell" -> * +"name" -> "My name is Haroun. At your service." +"haroun" -> "That is the name my honourable father chose for me." +"job" -> "I am a merchant. Though I have not chosen this position for myself I have accepted the task with humility and devotion. ...", + "If you have the permission of Gabel to trade with me, I can sell you some useful magical equipment." +"permission" -> "I am not allowed to trade with you unless Gabel gave you the permission to trade with us." + +"trade" -> "It is my mission to trade items on behalf of our community. ...", + "Just ask me for my current offers." +"merchant" -> * +"gabel" -> "He is our leader. Not because he desires power or distinction, of course, but because the community needs a guide in spiritual and in worldly matters. ...", + "He has accepted this burden with the modesty that is required of a true follower of Daraman." +"king" -> "We do not have kings. Of the members of our community, Gabel is closest to being what you would consider a leader, but he would resent being called by that presumptuous title." +"djinn" -> "That is my race. I like to compare it to iron. It has the potential to be forged into the finest steel, but it does need a lot of work and devotion. ...", + "Without that it is nothing but worthless scrap metal. Like our master said in the scriptures: Forge thyself! Be a blade of the true creed! (Book VI, chapter 14, verses 3 and 4)." +"efreet" -> "Daraman has taught us restraint. But I am sure one day those apostates will pay dearly for their sinful rebellion!" +"marid" -> "We are the chosen ones. Those who walk the path to enlightenment with serenity and humility." +"malor" -> "That filthy heretic! To me he is the very embodiment of all the evilness and the baseness we djinn must leave behind." +"mal'ouquah" -> "That is the name of the Efreets' hideout. It certainly is one filthy hotbed of vice and degeneration." +"ashta'daramai" -> "This is the name of the humble abode you are currently visiting. After Daraman had shown him the true way Gabel decided to tear down his former palace and to erect the present structure. ...", + "Its name is meant as a tribute to the invaluable service Daraman has done to our race." +"human" -> "Back in the dark days we used to be enemies, the humans and us. But then a human came and radically changed the way we see things. ...", + "He taught us to see your race as brothers. Of course... of course we still have lessons to learn, I suppose." + +"zathroth" -> "An evil, evil god. He was the creator of my race, but he rejected us because we were not evil enough and far too independent to his taste. For this reason the djinn do not worship him anymore, not even those depraved efreet. ...", + "It is difficult, you know. We djinn had to learn to find our own way, to create a destiny of our own. And although we are not evil by nature the fact that we were created by this fiend was lying like a curse on us. ...", + "For a long time we walked in darkness, filled with hate of ourselves and of all creation. But then everything changed. A human came and made us realise how blind we were. ...", + "Praise the gods who have spoken to us through Daraman! That human taught us how we could overcome our grim heritage and heal the scars that marred our souls. Humility and asceticism are the keys to a fuller existence. ...", + "The task may be tedious, but the price that awaits the disciples of the true creed is worth it all." + +"tibia" -> "Despite all the evil that stalks this world it is still a source of endless marvel and joy to me. Like the master said: ...", + "'Look at the world and reflect in admiration. For there is peace in a sunset and wisdom in the ocean's neverending melody.' (Book I, chapter 3, verses 2 and 3)." +"daraman" -> "How could my unworthy tongue ever find words to praise the enlightened one the way he deserves to be praised? Daraman took the veil from our eyes. He made us realise the extent of our misery. ...", + "However, he did much more than that. He showed us a way out. He gave meaning to our lives. The service he has done to my race will never be forgotten." +"darashia" -> "I understand the humans living there are self-indulgent and decadent. I am sure Daraman would disapprove of their ways." +"scarab" -> "The scarab is an ancient species. It's chitinous shell can be turned into excellent armour." +"edron" -> "They say the humans have founded impressive communities to the north. I hope their spiritual integrity matches their entrepreneurial spirit." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "Ankrahmun has a venerable tradition, but now that heretic fool has acceeded to the throne it has degenerated into a breeding place for wicked heresy." +"pharaoh" -> "I have heard many a gruesome stories about this presumptuous fool, who calls himself a pharaoh. Such hubris! Daraman himself, who was certainly the most divine mortal that ever walked Tibia, would never have dared to call himself a god." +"palace" -> "I am a peaceful djinn, but with all those stories I have heard I really feel like visiting the palace and to cleanse this place from all the evil and the misbelief infesting that place." +"ascension" -> "I have heard this is what the pharaoh has promised his followers. It is sad to see this wicked sinner is still allowed to spread his heresy!" +"rah" -> "Another one of the pharaoh's confused ideas." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "The Kha'zeel mountains have been created by the gods as a vantage point from where they could watch their creation. ...", + "Isn't it uplifting to think that even now we are standing on ground that was blessed by the great creators." +"kha'labal" -> "Kha'labal. It makes me sad just to hear that name. Long, long time ago this used to be a garden, you know? ...", + "You should have seen it then. You should have seen the springs and the meadows and the flowers. It was a paradise. . Oh, this terrible war." +"melchior" -> "I remember Malchior. I used to meet him regularly, but strictly in matters of business. He was not really a likeable fellow then. ...", + "All money and profit he was, and not as quite as clever as he thought. He made quite a pretty penny by doing business both with us and the efreet. ...", + "He thought we wouldn't notice, but the truth is we just chose to look the other way." +"alesar" -> "Who? Yes, I... I have never... I mean, I don't know who that is." +"fa'hradin" -> "Fa'hradin is a learned man, and this alone is enough to make me respect him. However, I sometimes can't help the feeling that he is lacking spiritual strength." +"bo'ques" -> "Bo'ques is a cook. Cooking! What a useless craft. If I were Gabel I would have little use for a cook such as him, but perhaps not all djinns are as spiritual as Daraman would have them be." +"lamp" -> "The lamp is a djinn's resting place of choice." + +"weapon" -> "I'm afraid I do not trade with weapons or armour. Nah'bob only deals with magical equipment." +"armor" -> * +"armour" -> * +"wares" -> "I only deal with magical equipment. Our range of goods include amulets, rings, wands and some special items." +"offer" -> * +"goods" -> * +"equipment" -> * +"magical" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"amulets" -> "I'm selling and buying bronze amulets, stone skin amulets, elven amulets and garlic necklaces." +"rings" -> "I'm selling and buying stealth rings, power rings, sword rings, axe rings, and club rings." +"wands" -> "I'm buying wands of vortex, wands of dragonbreath, wands of plague, wands of cosmic energy and wands of inferno as well as magic light wands." +"rods" -> "I'm not interested in rods." +"special" -> "I'm selling and buying magic light wands. I'm currently also looking for mind stones, life crystals and orbs." + +"light" -> Type=3046, Amount=1, Price=120, "Do you want to buy a magic light wand for %P gold?", Topic=10 +"sword","ring" -> Type=3091, Amount=1, Price=500, "Do you want to buy a sword ring for %P gold?", Topic=10 +"axe","ring" -> Type=3092, Amount=1, Price=500, "Do you want to buy an axe ring for %P gold?", Topic=10 +"club","ring" -> Type=3093, Amount=1, Price=500, "Do you want to buy a club ring for %P gold?", Topic=10 +"stone","skin","amulet" -> Type=3081, Amount=1, Price=5000, "Do you want to buy a stone skin amulet for %P gold?", Topic=10 +"elven","amulet" -> Type=3082, Amount=1, Price=500, "Do you want to buy an elven amulet for %P gold?", Topic=10 +"garlic","necklace" -> Type=3083, Amount=1, Price=100, "Do you want to buy a garlic necklace for %P gold?", Topic=10 +"bronze","amulet" -> Type=3056, Amount=1, Price=100, "Do you want to buy a bronze amulet for %P gold?", Topic=10 +"stealth","ring" -> Type=3049, Amount=1, Price=5000, "Do you want to buy a stealth ring for %P gold?", Topic=10 +"power","ring" -> Type=3050, Amount=1, Price=100, "Do you want to buy a power ring for %P gold?", Topic=10 + +%1,1<%1,"light" -> Type=3046, Amount=%1, Price=120*%1, "Do you want to buy %A magic light wands for %P gold?", Topic=10 +%1,1<%1,"power","ring" -> Type=3050, Amount=%1, Price=100*%1, "Do you want to buy %A power rings for %P gold?", Topic=10 +%1,1<%1,"bronze","amulet" -> Type=3056, Amount=%1, Price=100*%1, "Do you want to buy %A bronze amulets for %P gold?", Topic=10 +%1,1<%1,"sword","ring" -> Type=3091, Amount=%1, Price=500*%1, "Do you want to buy %A sword rings for %P gold?", Topic=10 +%1,1<%1,"axe","ring" -> Type=3092, Amount=%1, Price=500*%1, "Do you want to buy %A axe rings for %P gold?", Topic=10 +%1,1<%1,"club","ring" -> Type=3093, Amount=%1, Price=500*%1, "Do you want to buy %A club rings for %P gold?", Topic=10 +%1,1<%1,"elven","amulet" -> Type=3082, Amount=%1, Price=500*%1, "Do you want to buy %A elven amulets for %P gold?", Topic=10 +%1,1<%1,"garlic","necklace" -> Type=3083, Amount=%1, Price=100*%1, "Do you want to buy %A garlic necklaces for %P gold?", Topic=10 +%1,1<%1,"stone","skin","amulet" -> Type=3081, Amount=%1, Price=5000*%1, "Do you want to buy %A stone skin amulets for %P gold?", Topic=10 +%1,1<%1,"stealth","ring" -> Type=3049, Amount=%1, Price=5000*%1, "Do you want to buy %A stealth rings for %P gold?", Topic=10 + +"sell","light" -> Type=3046, Amount=1, Price=35, "Do you want to sell a magic light wand for %P gold?", Topic=11 +"sell","sword","ring" -> Type=3091, Amount=1, Price=100, "Do you want to sell a sword ring for %P gold?", Topic=11 +"sell","axe","ring" -> Type=3092, Amount=1, Price=100, "Do you want to sell an axe ring for %P gold?", Topic=11 +"sell","club","ring" -> Type=3093, Amount=1, Price=100, "Do you want to sell a club ring for %P gold?", Topic=11 +"sell","stone","skin","amulet" -> Type=3081, Amount=1, Price=500, "Do you want to sell a stone skin amulet for %P gold?", Topic=11 +"sell","elven","amulet" -> Type=3082, Amount=1, Price=100, "Do you want to sell an elven amulet for %P gold?", Topic=11 +"sell","garlic","necklace" -> Type=3083, Amount=1, Price=50, "Do you want to sell a garlic necklace for %P gold?", Topic=11 +"sell","bronze","amulet" -> Type=3056, Amount=1, Price=50, "Do you want to sell a bronze amulet for %P gold?", Topic=11 +"sell","stealth","ring" -> Type=3049, Amount=1, Price=200, "Do you want to sell a stealth ring for %P gold?", Topic=11 +"sell","power","ring" -> Type=3050, Amount=1, Price=50, "Do you want to sell a power ring for %P gold?", Topic=11 +"sell","mind","stone" -> Type=3062, Amount=1, Price=100, "Do you want to sell a mind stone for %P gold?", Topic=11 +"sell","life","crystal" -> Type=3061, Amount=1, Price=50, "Do you want to sell a life crystal for %P gold?", Topic=11 +"sell","orb" -> Type=3060, Amount=1, Price=750, "Do you want to sell an orb for %P gold?", Topic=11 + +"sell",%1,1<%1,"light" -> Type=3046, Amount=%1, Price=35*%1, "Do you want to sell %A magic light wands for %P gold?", Topic=11 +"sell",%1,1<%1,"power","ring" -> Type=3050, Amount=%1, Price=50*%1, "Do you want to sell %A power rings for %P gold?", Topic=11 +"sell",%1,1<%1,"bronze","amulet" -> Type=3056, Amount=%1, Price=50*%1, "Do you want to sell %A bronze amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"sword","ring" -> Type=3091, Amount=%1, Price=100*%1, "Do you want to sell %A sword rings for %P gold?", Topic=11 +"sell",%1,1<%1,"axe","ring" -> Type=3092, Amount=%1, Price=100*%1, "Do you want to sell %A axe rings for %P gold?", Topic=11 +"sell",%1,1<%1,"club","ring" -> Type=3093, Amount=%1, Price=100*%1, "Do you want to sell %A club rings for %P gold?", Topic=11 +"sell",%1,1<%1,"elven","amulet" -> Type=3082, Amount=%1, Price=100*%1, "Do you want to sell %A elven amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"garlic","necklace" -> Type=3083, Amount=%1, Price=50*%1, "Do you want to sell %A garlic necklaces for %P gold?", Topic=11 +"sell",%1,1<%1,"stone","skin","amulet" -> Type=3081, Amount=%1, Price=500*%1, "Do you want to sell %A stone skin amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"stealth","ring" -> Type=3049, Amount=%1, Price=200*%1, "Do you want to sell %A stealth rings for %P gold?", Topic=11 +"sell",%1,1<%1,"mind","stone" -> Type=3062, Amount=%1, Price=100*%1, "Do you want to sell %A mind stones for %P gold?", Topic=11 +"sell",%1,1<%1,"life","crystal" -> Type=3061, Amount=%1, Price=50*%1, "Do you want to sell %A life crystals for %P gold?", Topic=11 +"sell",%1,1<%1,"orb" -> Type=3060, Amount=%1, Price=750*%1, "Do you want to sell %A orbs for %P gold?", Topic=11 + +"sell","wand","of","vortex" -> Type=3074, Amount=1, Price=100, "Do you want to sell a wand of vortex for %P gold?", Topic=11 +"sell","wand","of","dragonbreath" -> Type=3075, Amount=1, Price=200, "Do you want to sell a wand of dragonbreath for %P gold?", Topic=11 +"sell","wand","of","plague" -> Type=3072, Amount=1, Price=1000, "Do you want to sell a wand of plague for %P gold?", Topic=11 +"sell","wand","of","cosmic","energy" -> Type=3073, Amount=1, Price=2000, "Do you want to sell a wand of cosmic energy for %P gold?", Topic=11 +"sell","wand","of","inferno" -> Type=3071, Amount=1, Price=3000, "Do you want to sell a wand of inferno for %P gold?", Topic=11 + +"sell",%1,1<%1,"wand","of","vortex" -> Type=3074, Amount=%1, Price=100*%1, "Do you want to sell %A wands of vortex for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","dragonbreath" -> Type=3075, Amount=%1, Price=200*%1, "Do you want to sell %A wands of dragonbreath for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","plague" -> Type=3072, Amount=%1, Price=1000*%1, "Do you want to sell %A wands of plague for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","cosmic","energy" -> Type=3073, Amount=%1, Price=2000*%1, "Do you want to sell %A wands of cosmic energy for %P gold?", Topic=11 +"sell",%1,1<%1,"wand","of","inferno" -> Type=3071, Amount=%1, Price=3000*%1, "Do you want to sell %A wands of inferno for %P gold?", Topic=11 + +Topic=10,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Thank you. May it bring you luck!", DeleteMoney, Create(Type) +Topic=10,"yes" -> "I hate to disappoint you, but it seems you do not have enough gold." +Topic=10 -> "I understand. Perhaps another time then." + +Topic=11,QuestValue(283)<3,! -> "I'm sorry, human. But you need Gabel's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Thank you. Here is your money.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one." +Topic=11,"yes",Amount>1 -> "You do not have that many." +Topic=11 -> "I understand. Perhaps another time then." + +} diff --git a/data/npc/harsky.npc b/data/npc/harsky.npc new file mode 100644 index 0000000..5b32c5c --- /dev/null +++ b/data/npc/harsky.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# harsky.npc: Datenbank für den Wachmann Harsky + +Name = "Harsky" +Outfit = (131,79-79-79-79) +Home = [32312,32170,6] +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,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","king",! -> "Wait for your audience!" +BUSY,"hail$","king",! -> "Wait for your audience!" +BUSY,"salutations$","king",! -> "Wait for your audience!" +BUSY,"hi$","king",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/data/npc/helor.npc b/data/npc/helor.npc new file mode 100644 index 0000000..bdbed88 --- /dev/null +++ b/data/npc/helor.npc @@ -0,0 +1,103 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# helor.npc: Datenbank für den Paladinlehrer Helor + +Name = "Helor" +Outfit = (134,57-79-95-98) +Home = [32572,32753,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted in the name of the gods, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Learn to show patience.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods watch over you." + +"bye" -> "May the gods watch you.", Idle +"farewell" -> * +"job" -> "I am a paladin and a teacher." +"name" -> "My name is Helor." +"time" -> "It is %T right now." +"king" -> "Our king will learn about the things happening here and he will be not amused." +"venore" -> "Those tradesmen would gladly sell their souls. And they would sell them cheap." +"thais" -> "Thais has its mistakes but it's a town's people that form a society and it's its people that have to be blamed for a society's failure." +"carlin" -> "In their own way they are still following the word of the gods." +"edron" -> "There are certain problems in Edron for sure and the defection of some of the knights was a great loss and caused much shame. But we are growing on the obstacles we have to overcome." +"jungle" -> "The jungle is a challenge and even here in this city you can feel its corruptive influence. It's always lurking to crush the ones that are weak in body or mind." + +"tibia" -> "The face of the world was sculpted by conflicts of the gods and the mortals." + +"kazordoon" -> "Dwarves abandoned the gods because they are shortsighted. They are lost people." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The believes of the elves are just a pack of lies to comfort their vanity. Only the gods have the power to elevate us beyond the restrictions of our mortal form. The elves' vanity will lead them to nothing." +"elves" -> * +"elfs" -> * +"darama" -> "It's up to us to fulfil the will of the gods even here at this remote continent." +"darashia" -> "The people there are not evil, they just follow a terribly wrong philosophy." +"ankrahmun" -> "An abnormality leading an abnormal cult. The day will come where our forces are strong enough to cleanse the city and the minds of the people." +"ferumbras" -> "Evil has many faces. He is only one of them." +"excalibug" -> "A weapon that should be used to slay evil wherever it shows its ugly face." +"apes" -> "They are intelligent enough to raid Port Hope in order to steal tools, so unlike other animals they are responsible for their wrongdoing and should be punished." +"lizard" -> "The lizards are aggressive enemies. It's obvious they never heard about our gods and their ideals." +"dworcs" -> "They are just another breed of orcs and they will be treated like them." +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "May the gods watch you.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/data/npc/hl.npc b/data/npc/hl.npc new file mode 100644 index 0000000..e3ad8b3 --- /dev/null +++ b/data/npc/hl.npc @@ -0,0 +1,352 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hl.npc: Datenbank für den Aufkäufer im Kriminellencamp + +Name = "H.L." +Outfit = (131,12-76-0-95) +Home = [32643,32212,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Hmmm. I buy weapons, armor, and other stuff. What do you want, %N?", Data=3303 +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "I don't serve brats. Sod off!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "HEY! Wait in the queue, %N.", Queue +BUSY,"hi$",male,! -> * +BUSY,"hello$",female,! -> "I don't serve brats. Sod off!" +BUSY,"hi$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "Bye" + +"bye",female -> "Get lost, stinky dragon.", Idle +"bye",male -> "Bye.", Idle +"job" -> "I buy all kinds of armory and weapons." +"name" -> "Won't tell you." +"h.l." -> "That's me." +"snake","eye" -> "Boss of the tavern. He's alright." +"boss" -> "Snake Eye isn't my boss." +"tavern" -> "Drink and eat there. What else do you do in a tavern!" +"brat" -> "Bah. Women are not good for fighting. I don't need them. And I don't like them." +"women" -> * +"woman" -> * + +"god" -> "Forget the gods" +"gods" -> * +"durin" -> "Forget Durin. He's the worst anyway." +"steve" -> "Forget Steve." +"guido" -> "Forget Guido." +"stephan" -> "Forget Stephan." +"cip" -> "Forget about Cip." + +"tibia" -> "Tibia. At least there's one good place in Tibia. Here!" +"thais" -> "Ha! Thais. I lived there. You know, I was in the royal army. But it's all wrong. I deserted." +"royal","army" -> "Good fighter training. But for the wrong cause." +"training" -> "Yes. Good training." +"cause" -> "Don't want to talk about it." +"talk" -> "I said, I do not want to talk about it", Topic=1 +Topic=1,"talk" -> "Ok. Get lost!", Idle + +"kazordoon" -> "Dwarfs are good people. I like them." +"dwarfs" -> "I like them." +"ab'dendriel" -> "Elves. Hate them." +"edron" -> "Might be a good place to live. But I'm afraid that the people are Thais friendly." +"king" -> "The king should be dead." +"ruler" -> "Tibia doesn't need a ruler." +"tibianus" -> "Hang him." + +"wild","warrior" -> "Yeah. I'm a wild warrior. Well, to be honest, I left them. They became too aggressive. Attacking everyone is not good." +"camp" -> "Most people in the camp are no wild warriors." +"hid" -> "Wild warriors have always something to hide." +"key" -> "What key? Show me!" +"key",Count(2970)>0 -> "Oh. that's a new key. Hmmm. Must be for the new hideout." +"hideout" -> "I left the wild warriors, while we - well - they planned a new hideout." +"new" -> "It's somewhere in the woods, of course. I don't know where." +"woods" -> "The woods are good to hide." +"building" -> "You mean our old building in the southwest?", Topic=2 +Topic=2,"yes" -> "That's the old hideout. It's interesting down there. Lots of security mechanics and traps. But it collapsed partly." +Topic=2,"no" -> "Sorry." + +"mechanics" -> "Yes. Security doors driven by POWERFUL machines. But I have no idea how it works.", Topic=7 +"machines" -> * +"traps" -> "Be careful out there." +"collapsed" -> "Yes. That's why we - well - they planned a new hideout. But I think they left the vault in the old hideout." +"vault" -> "Good stuff in there, I think." +"stuff" -> "Ahm. I don't know what it is. Sorry." +Topic=7, "broken" -> "Hmmm. Let me think. I think, you need something big. And steel reinforced. A barrel, maybe." +Topic=7, "damaged" -> * +Topic=7, "repair" -> * + +"sell" -> "I buy nearly everything. Just ask." + +# Ankauf von Waffen, Nummern 0-57 +"sell","sword" -> Type=3264, Amount=1, Price=7 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","battle","axe" -> Type=3266, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dagger" -> Type=3267, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","hand","axe" -> Type=3268, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","halberd" -> Type=3269, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","club" -> Type=3270, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","spike","sword" -> Type=3271, Amount=1, Price=25 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","rapier" -> Type=3272, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","sabre" -> Type=3273, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","axe" -> Type=3274, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","double","axe" -> Type=3275, Amount=1, Price=70 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","hatchet" -> Type=3276, Amount=1, Price=7 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","spear" -> Type=3277, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","magic","longsword" -> Type=3278, Amount=1, Price=460, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","war","hammer" -> Type=3279, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","fire","sword" -> Type=3280, Amount=1, Price=335, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","giant","sword" -> Type=3281, Amount=1, Price=100, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","morning","star" -> Type=3282, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","ice","rapier" -> Type=3284, Amount=1, Price=250, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","longsword" -> Type=3285, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","mace" -> Type=3286, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","throwing","star" -> Type=3287, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","magic","sword" -> Type=3288, Amount=1, Price=350, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","staff" -> Type=3289, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","silver","dagger" -> Type=3290, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","knife" -> Type=3291, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","combat","knife" -> Type=3292, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","sickle" -> Type=3293, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","short","sword" -> Type=3294, Amount=1, Price=3 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","bright","sword" -> Type=3295, Amount=1, Price=280, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","warlord","sword" -> Type=3296, Amount=1, Price=360, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","serpent","sword" -> Type=3297, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","throwing","knife" -> Type=3298, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","poison","dagger" -> Type=3299, Amount=1, Price=5 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","katana" -> Type=3300, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","broadsword" -> Type=3301, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","lance" -> Type=3302, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","great","axe" -> Type=3303, Amount=1, Price=300, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","crowbar" -> Type=3304, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","sickle" -> Type=3306, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","scimitar" -> Type=3307, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","machete" -> Type=3308, Amount=1, Price=6 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","thunder","hammer" -> Type=3309, Amount=1, Price=450, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","iron","hammer" -> Type=3310, Amount=1, Price=9 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","clerical","mace" -> Type=3311, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","silver","mace" -> Type=3312, Amount=1, Price=270, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","obsidian","lance" -> Type=3313, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","naginata" -> Type=3314, Amount=1, Price=80 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","guardian","halberd" -> Type=3315, Amount=1, Price=120, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","orcish","axe" -> Type=3316, Amount=1, Price=12 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","barbarian","axe" -> Type=3317, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","axe" -> Type=3318, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","stonecutter","axe" -> Type=3319, Amount=1, Price=320, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","fire","axe" -> Type=3320, Amount=1, Price=280, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=7*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=25*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=70*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=7*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"magic","longsword" -> Type=3278, Amount=%1, Price=460*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=335*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"giant","sword" -> Type=3281, Amount=%1, Price=100*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=250*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"magic","sword" -> Type=3288, Amount=%1, Price=350*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"silver","dagger" -> Type=3290, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"knife" -> Type=3291, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"combat","knife" -> Type=3292, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"knives" -> Type=3291, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"combat","knives" -> Type=3292, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=3*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"bright","sword" -> Type=3295, Amount=%1, Price=280*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"warlord","sword" -> Type=3296, Amount=%1, Price=360*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"serpent","sword" -> Type=3297, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"throwing","knife" -> Type=3298, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"throwing","knives" -> Type=3298, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"poison","dagger" -> Type=3299, Amount=%1, Price=5*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"katana" -> Type=3300, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"broadsword" -> Type=3301, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","lance" -> Type=3302, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"great","axe" -> Type=3303, Amount=%1, Price=300*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","sickle" -> Type=3306, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"scimitar" -> Type=3307, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=6*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"thunder","hammer" -> Type=3309, Amount=%1, Price=450*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"iron","hammer" -> Type=3310, Amount=%1, Price=9*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"clerical","mace" -> Type=3311, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"silver","mace" -> Type=3312, Amount=%1, Price=270*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"naginata" -> Type=3314, Amount=%1, Price=80*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"guardian","halberd" -> Type=3315, Amount=%1, Price=120*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"orcish","axe" -> Type=3316, Amount=%1, Price=12*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"barbarian","axe" -> Type=3317, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","axe" -> Type=3318, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"stonecutter","axe" -> Type=3319, Amount=%1, Price=320*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"fire","axe" -> Type=3320, Amount=%1, Price=280*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +# Ankauf von Waffen (Bows), Nummern 1-2 +"sell","crossbow" -> Type=3349, Amount=1, Price=20 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","bow" -> Type=3350, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 + +"sell",%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=20*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 + +# Ankauf von Ruestung, Nummern 0-39 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=4 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=80 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","plate","armor" -> Type=3357, Amount=1, Price=110, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","chain","armor" -> Type=3358, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","armor" -> Type=3359, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","armor" -> Type=3360, Amount=1, Price=580, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","leather","armor" -> Type=3361, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","studded","legs" -> Type=3362, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","scale","legs" -> Type=3363, Amount=1, Price=180, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","legs" -> Type=3364, Amount=1, Price=120, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","golden","helmet" -> Type=3365, Amount=1, Price=420, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","magic","plate","armor" -> Type=3366, Amount=1, Price=720, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=12 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","winged","helmet" -> Type=3368, Amount=1, Price=320, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=75 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","armor" -> Type=3370, Amount=1, Price=140, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","knight","legs" -> Type=3371, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","legs" -> Type=3372, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","strange","helmet" -> Type=3373, Amount=1, Price=55 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","legion","helmet" -> Type=3374, Amount=1, Price=8 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","soldier","helmet" -> Type=3375, Amount=1, Price=16 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=2 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","scale","armor" -> Type=3377, Amount=1, Price=75 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","armor" -> Type=3378, Amount=1, Price=18 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","doublet" -> Type=3379, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","rose","armor" -> Type=3380, Amount=1, Price=140, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","armor" -> Type=3381, Amount=1, Price=210, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","legs" -> Type=3382, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","armor" -> Type=3383, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","helmet" -> Type=3384, Amount=1, Price=40 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","helmet" -> Type=3385, Amount=1, Price=70 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","scale","mail" -> Type=3386, Amount=1, Price=280, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","demon","helmet" -> Type=3387, Amount=1, Price=95 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","demon","armor" -> Type=3388, Amount=1, Price=195, "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","demon","legs" -> Type=3389, Amount=1, Price=84 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","horned","helmet" -> Type=3390, Amount=1, Price=155, "I buy this for %P gold. Is that ok?", Topic=6 + +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=4*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=80*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=110*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=580*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"studded","legs" -> Type=3362, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","scale","legs" -> Type=3363, Amount=%1, Price=180*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","legs" -> Type=3364, Amount=%1, Price=120*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"golden","helmet" -> Type=3365, Amount=%1, Price=420*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"magic","plate","armor" -> Type=3366, Amount=%1, Price=720*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=12*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"winged","helmet" -> Type=3368, Amount=%1, Price=320*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=75*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=140*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"strange","helmet" -> Type=3373, Amount=%1, Price=55*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"legion","helmet" -> Type=3374, Amount=%1, Price=8*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"soldier","helmet" -> Type=3375, Amount=%1, Price=16*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=2*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"scale","armor" -> Type=3377, Amount=%1, Price=75*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=18*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"rose","armor" -> Type=3380, Amount=%1, Price=140*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","armor" -> Type=3381, Amount=%1, Price=210*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","legs" -> Type=3382, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","armor" -> Type=3383, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","helmet" -> Type=3384, Amount=%1, Price=40*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","helmet" -> Type=3385, Amount=%1, Price=70*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","scale","mail" -> Type=3386, Amount=%1, Price=280*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"demon","helmet" -> Type=3387, Amount=%1, Price=95*%1 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"demon","armor" -> Type=3388, Amount=%1, Price=195*%1, "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"demon","legs" -> Type=3389, Amount=%1, Price=84*%1 , "Not bad. A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"horned","helmet" -> Type=3390, Amount=%1, Price=155*%1, "I buy this for %P gold. Is that ok?", Topic=6 + +# Ankauf von Schilden, Nummern 0-25 +"sell","steel","shield" -> Type=3409, Amount=1, Price=30 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","plate","shield" -> Type=3410, Amount=1, Price=25 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","brass","shield" -> Type=3411, Amount=1, Price=15 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","battle","shield" -> Type=3413, Amount=1, Price=50 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","mastermind","shield" -> Type=3414, Amount=1, Price=550, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","guardian","shield" -> Type=3415, Amount=1, Price=150, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=115, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","unholy","shield" -> Type=3417, Amount=1, Price=520, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","beholder","shield" -> Type=3418, Amount=1, Price=79 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","crown","shield" -> Type=3419, Amount=1, Price=109, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","demon","shield" -> Type=3420, Amount=1, Price=130, "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dark","shield" -> Type=3421, Amount=1, Price=60 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","great","shield" -> Type=3422, Amount=1, Price=480, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","blessed","shield" -> Type=3423, Amount=1, Price=650, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell","ornamented","shield" -> Type=3424, Amount=1, Price=45 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=55 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","studded","shield" -> Type=3426, Amount=1, Price=2 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell","rose","shield" -> Type=3427, Amount=1, Price=49 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","tower","shield" -> Type=3428, Amount=1, Price=90 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","black","shield" -> Type=3429, Amount=1, Price=5 , "Bah. That's disgusting. But I take it for %P gold. Ok?", Topic=6 +"sell","copper","shield" -> Type=3430, Amount=1, Price=10 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","viking","shield" -> Type=3431, Amount=1, Price=35 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","ancient","shield" -> Type=3432, Amount=1, Price=49 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","griffin","shield" -> Type=3433, Amount=1, Price=59 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell","vampire","shield" -> Type=3434, Amount=1, Price=119, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=30*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=25*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=15*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=1*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=50*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"mastermind","shield" -> Type=3414, Amount=%1, Price=550*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"guardian","shield" -> Type=3415, Amount=%1, Price=150*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=115*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"unholy","shield" -> Type=3417, Amount=%1, Price=520*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=79*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"crown","shield" -> Type=3419, Amount=%1, Price=109*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"demon","shield" -> Type=3420, Amount=%1, Price=130*%1, "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dark","shield" -> Type=3421, Amount=%1, Price=60*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"great","shield" -> Type=3422, Amount=%1, Price=480*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"blessed","shield" -> Type=3423, Amount=%1, Price=650*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"ornamented","shield" -> Type=3424, Amount=%1, Price=45*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=55*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=2*%1 , "I give you %P gold for this garbage. Ok?", Topic=6 +"sell",%1,1<%1,"rose","shield" -> Type=3427, Amount=%1, Price=49*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"tower","shield" -> Type=3428, Amount=%1, Price=90*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"black","shield" -> Type=3429, Amount=%1, Price=5*%1 , "Bah. That's disgusting. But I take it for %P gold. Ok?", Topic=6 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=10*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=35*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"ancient","shield" -> Type=3432, Amount=%1, Price=49*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"griffin","shield" -> Type=3433, Amount=%1, Price=59*%1 , "I buy this for %P gold. Is that ok?", Topic=6 +"sell",%1,1<%1,"vampire","shield" -> Type=3434, Amount=%1, Price=119*%1, "A rare item. But I can give you only %P gold. Ok?", Topic=6 + +#Ankauf +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you don't have one." +Topic=6,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=6 -> "Ok, then not." +} diff --git a/data/npc/hofech.npc b/data/npc/hofech.npc new file mode 100644 index 0000000..16db11e --- /dev/null +++ b/data/npc/hofech.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hofech.npc: Datenbank für den Möbelhändler Hofech in Darashia + +Name = "Hofech" +Outfit = (128,95-2-2-57) +Home = [33270,32441,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Have a look at my wares, my friend." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, just a moment, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Hofech Ibn Kalith." +"job" -> "I am selling furniture and equipment to grace the homes." +"time" -> "It's %T my friend." +"caliph" -> "The caliph has the finest home and interior decoration in Darashia." +"kazzan" -> * +"daraman" -> "The prophet is alive! Both in the heavens and in our our hearts." +"ferumbras" -> "I don't know about such a person, my friend." +"excalibug" -> "I think if that sword really existed, it would make a splendid decoration for any wall." +"thais" -> "The wood we are using to make our exquisite furniture is partly supplied by Thais." +"tibia" -> "One day I will take my flying carpet to see the whole world." +"carlin" -> "I was not there yet. But I plan to travel there one day." + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are extraordinarily cheap, given the fine quality of our stock. Just look at that table!" +"carpet" -> "No no, I don't sell any carpets." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/hoggle.npc b/data/npc/hoggle.npc new file mode 100644 index 0000000..a13f17d --- /dev/null +++ b/data/npc/hoggle.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hoggle.npc: Datenbank fuer den Fischer Hoggle + +Name = "Hoggle" +Outfit = (128,20-46-88-94) +Home = [32537,32143,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my humble home!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Can't you see I'm talking to someone?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am just a poor fisherman." +"name" -> "I am Hoggle. I live in this house." +"time" -> "No, this is not the time to go fishing." +"mountain" -> "Yes, there is a mountain to the north, but it's of no interest. There isn't any fish on it." +"food" -> "If you are hungry you can go downstairs, perhaps you will find some fish. You can also take some shoes if you want." +"map" -> "If you go north-west you will find Lubo and his adventurer shop. I think he sells maps." +"fisherman" -> "It's a very hard job, cause without a boat I have to swim and fish at the same time!" +"boat" -> "My boat sunk. I thought it would be more aerodynamic with holes in it." +"fish" -> "I think they can talk, but they are wise enough to be silent. Once I saw a mermaid." +"mermaid" -> "I saw one! She had the body of a fish, and also the head of a fish. Amazing!" +"stupid" -> "My mom always said, stupid is who stupid does." +"secret" -> "Can you keep a secret? I think fish can't breath on land!" +"thais" -> "I know this city. Sometimes I sell fish to Frodo." +"frodo" -> "He buys my fish." +"finger" -> "No, fish don't have fingers." +"pet","name" -> "Once there was a magician who named all his creatures like their species read backward." +"carlin" -> "There are stories about a city behind the mountain, but why should I go there? There is enough fish here." +} diff --git a/data/npc/hugo.npc b/data/npc/hugo.npc new file mode 100644 index 0000000..b87fc1f --- /dev/null +++ b/data/npc/hugo.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hugo.npc: Datenbank für den Besitzer des Modehauses Hugo Chief + +Name = "Hugo" +Outfit = (130,14-81-80-93) +Home = [32951,32103,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Perhaps we can chat later, %N." +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"name" -> "I am known as Hugo Chief." +"hugo" -> "Well it's not my real name. I took it because people think it's scaring and manly. I hate people doubting my manhood for being a tailor, you know." +"real","name" -> "Uhm, well it's Oscar Savage, but who can become famous, especially as an artist, with a name like that I ask you?" +"job" -> "I am tailor and designer extraordinaire." +"warehouse" -> "I would call it a 'wearhouse'." + +"uniform",QuestValue(233)=1 -> "A new uniform for the post officers? I am sorry but my dog ate the last dress pattern we used. You need to supply us with a new dress pattern." +"dress","pattern",QuestValue(233)=1 -> "It was ... wonderous beyond wildest imaginations! I have no clue where Kevin Postner got it from. Better ask him.",SetQuestvalue(233,2) +"dress","pattern",QuestValue(233)>1,QuestValue(233)<9 -> "I already told you to ask your boss about that issue." +"dress","pattern",QuestValue(233)=9 -> "By the gods of fashion! Didn't it do that I fed the last dress pattern to my poor dog? Will this mocking of all which is taste and fashion never stop?? Ok, ok, you will get those ugly, stinking uniforms and now get lost, fashion terrorist.",SetQuestvalue(233,10), Idle + +"uniform" -> "I don't get it, what uniforms you are talking about." + +"time" -> "Sorry, a watch would ruin my stylish outfit." +"king" -> "He does not care much about us, we don't care much about him. I consider that a fair deal." +"tibianus" -> * +"army" -> "I think they should not wear that ugly armor in town. I will see to assure that will be changed soon." +"ferumbras" -> "The ferumbras-bad-ass-fashion is incredibly outdated since years." +"excalibug" -> "I don't care for such fairytales." +"thais" -> "Thais is kind of a fashion hell. If there was an award for the most ugly citizens, it would go to Thais." +"tibia" -> "A world filled with ugly dressed people needs the skills of a fashion-hero." +"uglyness" -> * +"punished" -> * +"fashion","hero" -> "One day Captain Catwalk will punish all crimes to fashion and bring all ugly people to justice." +"captain","catwalk" -> "He's a supermodel! No one knows his secret identity!" +"supermodel" -> "A model with incredible superpowers. Do you think they call them supermodels for nothing?" +"carlin" -> "Women should know better than to hide in ugly armor. Like all followers of ugliness they will be punished one day." +"news" -> "My newest models are top secret, sorry." +"tax" -> "I don't care about such mundane things like 'taxes'." +"privilege" -> "The city was granted a few privileges by the king. I can't even tell which. They don't affect me that much." +"gambling" -> "I too love to gamble now and then in the Hard Rock tavern." +} diff --git a/data/npc/humgolf.npc b/data/npc/humgolf.npc new file mode 100644 index 0000000..95e942e --- /dev/null +++ b/data/npc/humgolf.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# humgolf.npc: Datenbank für den Rotwormzähmer Humgolf (Zwergenstadt) + +Name = "Humgolf" +Outfit = (69,0-0-0-0) +Home = [32598,31880,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "%N, good day .. or night, whatever." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N. We talk later if you insist.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye", Idle +"farewell" -> * +"job" -> "I am chief rotwormtamer of Kazordoon. I sell rotworms and buy meat and tasty, fresh rats for my worms." +"name" -> "I am Humgolf Molesight, Son of Earth, from the Molten Rock." +"tibia" -> "More nice beneath this noisy green surface." +"kazordoon" -> "I like the mines best." +"big","old" -> "The mountain seems tasty as far as my worms are concerned." +"worms" -> "They are so cute and so intelligent." +"humans" -> "They are not cute and not intelligent." +"orcs" -> "They are even more stupid and ugly than minotaurs." +"minotaurs" -> "They are stupid and ugly." +"elves" -> "They are not worth to be mentioned." +"geomancer" -> "They have an understanding of worms." +"god" -> "The worm does not care about gods, why should I?" +"earth" -> "Home of the worms, home of my people, too." +"fire" -> "Where earth is giving, fire is taking. That's the way of the elements." +"buy","rotworm" -> "Do you want to buy a rotworm?", topic=1 + +Topic=1,"yes" -> "Hey, you don't own a drilling licence. No deal!" +Topic=1 -> "You will regret that." + +"drilling","licence" -> "I am not allowed to sell worms to people without a formular 007 licence to drill or a 0815 artist licence." +"007" -> * +"0815" -> "It's a special licence for artists. It was only used once as I sold a white worm to Frietsiek and Yor." +"artist","licence" -> * + +"life" -> "Can't say I like it much." +"plant" -> "Only a rotting plant is a good plant." +"citizen" -> "Many noisy pepole down here are scaring my worms." +"kroox" -> "Poor guy, has lost his drilling licence for drinking." +"jimbin" -> "His tavern is too crowded and too bright for a dwarf with taste like me." +"maryza" -> "Don't like the way she looks at my worms." +"bezil" -> "Always chatting. How can someone talk that much?" +"nezil" -> * +"uzgod" -> "We are trading now and then. Fine dwarf he is." +"etzel" -> "Does a worm need spells to work his kind of magic? I do neither." +"motos" -> "That guy is a monster! I despise rotwormkillers." +"general" -> * +"durin" -> "If he'd live today he'd be a rotwormtamer like me." +"duria" -> "Thinks she's to good to talk to rotwormtamers." +"emperor" -> "The emperor should spend more money on rotworm husbandry." +"kruzak" -> * +"pyromancer" -> "Hotheads." +"technomancer" -> "GO AWAY! I heard they think of replacing worms with machines. That is an OUTRAGE!" +"army" -> "They should remember old dwarfish rotworm tactics. Think like a worm and the battle is almost won." +"colossus" -> "Never was up there to look at it." +"ferumbras" -> "A true enemy of the worms." +"excalibug" -> "Silly fairy tale." +"news" -> "Who needs news if the old things are still good enough?" +"monster" -> "Unwormish creatures they are." +"stone","golem" -> "Too hard to be gnawed away by even the finest worm." +"help" -> "I am here to help the worms, not the fools." +"quest" -> "What by the worm are you talking about?" +"task" -> * +"what","do" -> * +"gold" -> "Gold is one of the things my worms can unearth." +"money" -> * +"equipment" -> "If you own a good worm you need nothing else." +"time" -> "Time does not matter to a dwarf who understands the ways of the worm." + +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with that waterthing!" +"sell","rat" -> Type=3994, Amount=1, Price=2, "Do you have a fresh rat for sale?", Topic=2 + + +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A pieces of meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A pieces of ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "Do you have %A fresh rats for sale?", Topic=2 + + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"no" -> "Then not." +} diff --git a/data/npc/humphrey.npc b/data/npc/humphrey.npc new file mode 100644 index 0000000..623ea20 --- /dev/null +++ b/data/npc/humphrey.npc @@ -0,0 +1,73 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# humphrey.npc: Datenbank für den druiden humphrey + +Name = "Humphrey" +Outfit = (133,0-96-101-76) +Home = [32359,31683,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, child of nature." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am a guardian of nature. Also I am the keeper of the embrace of tibia, one of the five blessings." +"name" -> "My name is Humphrey." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking that bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you recrive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. They are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "You can ask for the blessing of spiritual shielding the whiteflower temple south of Thais." +"shielding" -> * +"spark" -> "The spark of the phoenix will be given to you by the dwarfen priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "I will give to you the embrace of tibia, but you will have to make a sacrifice. Are you prepared to pay 10.000 gold for the blessing?.",Price=10000, Topic=5 + +"fire" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(105) > 0 -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes" -> "So receive the embrace of tibia, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(105,1), Bless(2) +Topic=5 -> "Fine. You are free to decline my offer." + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." + +} + diff --git a/data/npc/hyacinth.npc b/data/npc/hyacinth.npc new file mode 100644 index 0000000..12403a4 --- /dev/null +++ b/data/npc/hyacinth.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# hyacinth.npc: Datenbank fuer den Druiden Hyacinth + +Name = "Hyacinth" +Outfit = (130,11-123-123-94) +Home = [32137,32171,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May Crunor bless you." + +"bye" -> "May Crunor bless you.", Idle +"farewell" -> * +"how","are","you" -> "Thanks to the gods, I am fine." +"sell" -> "I just sell some revitalizing life fluids." +"job" -> "I am a druid and healer, a follower of Crunor." +"name" -> "I am Hyacinth." +"time" -> "Time does not matter to me." +"help" -> "I can only sell life fluids, ask Cipfried for further help." +"monster" -> "Most of the so called monsters of this isle are just creatures of the gods. On the mainland there are some beasts that truly are monstrous." +"dungeon" -> "The dungeons are dangerous for unexperienced adventurers." +"sewer" -> "I rarely visit the town." +"god" -> "As far as I know there is a library in the village. Teach yourself about the gods." +"king" -> "I don't care about kings, queens, and the like." +"obi" -> "A greedy and annoying person as most people are." +"seymour" -> "He has some inner devils that torture him." +"dallheim" -> "A man of the sword." +"cipfried" -> "His healing powers equal even mine." +"amber" -> "I never talked to her longer." +"weapon" -> "I don't care much about weapons." +"magic" -> "I am one of the few magic users on this isle. But I sense a follower of the dark path of magic hiding somewhere in the depths of the dungeons." +"spell" -> "I can't teach you magic. On the mainland you will learn your spells soon enough." +"tibia" -> "It is shaped by the will of the gods, so we don't have to question it." + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=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=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=2 +"vial" -> * +"flask" -> * +Topic=2,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=2,"yes" -> "You don't have any empty vials." +Topic=2 -> "Hmm, but please keep Tibia litter free." +} diff --git a/data/npc/imalas.npc b/data/npc/imalas.npc new file mode 100644 index 0000000..1d65610 --- /dev/null +++ b/data/npc/imalas.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# imalas.npc: Datenbank für den Nahrungsmittelhändler Imalas + +Name = "Imalas" +Outfit = (128,115-10-39-114) +Home = [32334,31801,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I'm a shopkeeper. You can buy food here." +"name" -> "My name is Imalas." +"time" -> "Sorry, I have no watch." +"ghostlands" -> "Sorry I know nothing more then it has to be a horrible place and that scares me enough." + +"offer" -> "Just have a look at my blackboard." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"banana" -> Type=3587, Amount=1, Price=2, "Do you want to buy a banana for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"grapes" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=2, "Do you want to buy a carrot for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you want to buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you want to buy a roll for %P gold?", Topic=1 +"brown","bread" -> Type=3602, Amount=1, Price=3, "Do you want to buy a brown bread for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 + +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=2*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"grapes" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=2*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you want to buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"brown","bread" -> Type=3602, Amount=%1, Price=3*%1, "Do you want to buy %A brown breads for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/imbul.npc b/data/npc/imbul.npc new file mode 100644 index 0000000..935b99e --- /dev/null +++ b/data/npc/imbul.npc @@ -0,0 +1,89 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Imbul.npc: Datenbank für den Fährman Imbul + +Name = "Imbul" +Outfit = (128,95-2-63-115) +Home = [32558,32781,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I'm a ferryman. If you want me to transport you to the other end of the city, feel free to ask me for a passage." +"name" -> "My name is Imbul." +"time" -> "Sorry, I don't own a watch." +"king" -> "It must be fun to be a king." +"venore" -> "Here are many people from Venore. I used to live there but I lost my job and took the chance to come here." +"thais" -> "It's where the king lives. The roads there must be made of gold or marble at least." +"carlin" -> "It's a city, I know that." +"edron" -> "Edron is some isle where the richest knights and sorcerers live. There they are not annoyed of the constant begging of poor people." +"jungle" -> "We already lost many settlers to the jungle. No one knows who is next." + +"tibia" -> "That is our world, yes." + +"kazordoon" -> "I think those dwarves came from Kazordoon." +"dwarves" -> "The dwarves that live here are searching for gold." +"dwarfs" -> * +"ab'dendriel" -> "What is that?" +"elves" -> "The elven queen is the most beautiful woman in Tibia." +"elfs" -> * +"darama" -> "Many came here to make their fortune. But it might take a while to become rich." +"darashia" -> "That's somewhere behind that mountain." +"ankrahmun" -> "I heard it's a ghost town or something like that." +"ferumbras" -> "Why have some magicians to become evil?" +"excalibug" -> "I never heard about that." +"apes" -> "They stole my paddle once." +"lizard" -> "If you follow the river far enough upcountry, you might see a lizardman. But be careful, they'll attack you as soon as they catch sight of you." +"dworcs" -> "They are all murderers and cannibals." + + +"trip" -> "I can bring you either to the east end of Port Hope or to the centre of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"east" -> Price=7, "Do you seek a passage to the east end of Port Hope for %P gold?", Topic=1 +"cent" -> Price=7, "Do you seek a passage to the centre of Port Hope for %P gold?", Topic=2 + + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + +#"trip" -> Price=7, "Would you like to travel to the other end of Port Hope or to the centre of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * + +#Topic=1,"end",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"centre",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"end",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +#Topic=1,"end",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"centre",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +#Topic=1,"centre",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "Did the dworcs have your brain for supper? I was asking you WHERE you want to travel!" + + +} diff --git a/data/npc/irea.npc b/data/npc/irea.npc new file mode 100644 index 0000000..456a27f --- /dev/null +++ b/data/npc/irea.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# irea.npc: Datenbank für die Bognerin Irea (Elfenstadt) + +Name = "Irea" +Outfit = (64,0-0-0-0) +Home = [32688,31610,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I sell bows, arrows, crossbows and bolts. I also teach some spells." +"name" -> "I am known as Irea." +"time" -> "That's unimportant." + +"carlin" -> "The druids of Carlin seek our guidance now and then." +"thais" -> "I don't understand what interest this humans form a far away land have in our town." +"venore" -> "Their traders are verry intrusive." +"roderick" -> "What is this humans use at all? I don't understabd it." +"olrik" -> "His trade seems to be the delivery of messages and items." + +"elves" -> "Humans or dwarfs will never understand us." +"dwarfs" -> "Bearded, heavy, and small." +"humans" -> "Humans have so little time to learn." +"troll" -> "I despise them." + +"cenath" -> "I often listen to their tales." +"kuridai" -> "They provide us with tools and metal." +"deraisim" -> "My people love the woods." +"abdaisim" -> "One day we will be reunited." +"teshial" -> "They have left so long ago." +"ferumbras" -> "Who is that?" +"crunor" -> "The master of nature. He nurtures us and is our benevolent protector." +"excalibug" -> "Our people have a bugfarm in the southeast of Ab'Dendriel." +"news" -> "My news are not for your ears." + +"magic" -> "I teach spells to create enchanted arrows." +"spell" -> "I teach 'Conjure Arrow', 'Poison Arrow', and 'Explosive Arrow'." + +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 + +"conjure","arrow" -> "I'm sorry, but this spell is only for paladins." +"poison","arrow" -> * +"explosive","arrow" -> * + +"bow" -> Type=3350, Amount=1, Price=350, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=450, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=350*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=450*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Hey, you do not have enough gold." +Topic=1 -> "Maybe we will make the deal another time." + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have the gold to pay me." +Topic=3,"yes" -> "You have learned it now.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." +} diff --git a/data/npc/ironeye.npc b/data/npc/ironeye.npc new file mode 100644 index 0000000..5d14234 --- /dev/null +++ b/data/npc/ironeye.npc @@ -0,0 +1,42 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ironeye.npc: Datenbank für den Waffen und Rüstungshandelsfürsten Abran Ironeye + +Name = "Abran Ironeye" +Outfit = (73,0-0-0-0) +Home = [32907,32116,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hail, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Stand still and wait!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! I did not dismiss you!" + +"bye" -> "You are dismissed.", Idle +"name" -> "I am Abran Ironeye." +"job" -> "I am a busy man. I run the Ironhouse." +"ironhouse" -> "What do you think? Here weapons and armor are forged, repaired, and sold." +"time" -> "You should know that on your own!" +"king" -> "I would like to see a true warrior-king in Thais... like in the old days. But who knows, perhaps one day the tides of fate will bring such a man to power. Who knows..." +"tibianus" -> * +"army" -> "Such a great tool wasted for garrison duties, a shame." +"ferumbras" -> "Quite a challenge, but his bets for power were made without the finesse of a true warrior." +"finesse" -> "I won't give away my tricks, learn your own." +"excalibug" -> "If someone would bring me that weapon I could reshape the realm... and reward this hero beyond his dreams." +"ironeye" -> "I don't care if you like it or not. Stop staring at it!" +"teddy" -> "I don't know anything about a teddy... and if you are smart you shouldn't either..." +"thais" -> "Thais has outlived its usefulness since years. Its star is sinking." +"tibia" -> "The world is ready for a significant change." +"warehouse" -> "My Ironhouse is more than a warehouse." +"carlin" -> "Their independence is a proof for the weakness of Thais." +"news" -> "In Venore nothing comes for free and you could not afford my 'news'." +"tax" -> "The taxing keeps the Thaian kingdom alive, but it also might break its neck one day." +"banor" -> "As a man that grows up needs no mommy, a warrior has to outgrow his need for gods." +"privilege" -> "Venore's privileges are hard earned." +"gambling" -> "I trust only in skill, not luck." +} diff --git a/data/npc/ishebad.npc b/data/npc/ishebad.npc new file mode 100644 index 0000000..89483c3 --- /dev/null +++ b/data/npc/ishebad.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ishebad.npc: Datenbank für den großwesir ankrahmuns + +Name = "Ishebad" +Outfit = (65,0-0-0-0) +Home = [33158,32848,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"hi",! -> * +ADDRESS,"salutations",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "This person will never achieve ascension that way!" + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"name" -> "I am Ishebad the chosen." +"time" -> "Time does not matter to the undead." + +"temple" -> "The temple will take care of your spiritual matters." +"pharaoh" -> "Our immortal ruler, may he be blessed, is the keeper of our enlightenment and our saviour." +"ashmunrah" -> "The fallen pharaoh did not see it was time to step back and let his son rule. So he met the fate that he deserved." +"scarab" -> "The scarabs are keepers of secrets. Some secrets are not ment for your mortals. Ever keep that in mind." + +"tibia" -> "This world just awaits the wisdom of our pharaoh. It needs that wisdom and will soon learn to appreciate it." +"carlin" -> "Other cities are of no importance. Ankrahmun will become the center of the known world anyways." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> * +"ab'dendriel" -> * +"darama" -> "The rule of our beloved pharaoh will soon spread this continent and one day the whole known world." +"darashia" -> "This village is so insignificant that our wise pharaoh has choosen to ignore it." +"daraman" -> "Some lunatic who was driven mad by the heat of the desert and dehydration." +"Ankrahmun" -> "Our city will become the capital of a worldwide empire." +"Arkhothep" -> "The pharaoh wants not to be disturbed. I am his grand vizier and responsible for the daily affairs of the city and promotions of heroes." +"pharaoh" -> * +"job" -> * +"mortality" -> "If you please our pharaoh, he will reward you and free you from your mortality." +"ascension" -> "Consult a priest to learn how you could achieve ascension." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * +"undead" -> "Undeath is only for the choosen." +"undeath" -> * +"mourn" -> "You mortals are all to be mourned for your miserable existance." + + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/data/npc/ishina.npc b/data/npc/ishina.npc new file mode 100644 index 0000000..7211219 --- /dev/null +++ b/data/npc/ishina.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ishina.npc: Datenbank für die Juwelierin Ishina + +Name = "Ishina" +Outfit = (138,95-9-87-95) +Home = [33231,32423,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I will talk to you in a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings and good bye." + +"bye" -> "Daraman's blessings and good bye.", Idle +"job" -> "I am a jeweller. Maybe you want to have a look at my wonderful offers." +"name" -> "My name is Ishina." +"time" -> "Currently it is %T." +"caliph" -> "The caliph buys the most precious gems and jewellery for himself." +"kazzan" -> * +"daraman" -> "Oh, I am not an expert in mythology and philosophy. Better ask the enlightened Kasmir about this." +"kasmir" -> "You will find Kasmir in the Muhayin. He's a philosopher and teacher in the ways of Daraman." +"muhayin" -> "It's the sacred tower. A place of solitude and meditation." +"offer" -> "I can offer you various gems, pearls, or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and 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." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/isimov.npc b/data/npc/isimov.npc new file mode 100644 index 0000000..877f29a --- /dev/null +++ b/data/npc/isimov.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# isimov.npc: Datenbank für den Zwergenmönch Isimov (Zwergenstadt) + +Name = "Isimov" +Outfit = (160,115-0-19-95) +Home = [32653,31925,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N and greetings my child!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please be patient, %N. I'll be with you in moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "No patience, no manners." + +"bye" -> "Take care, %N!", Idle +"farewell" -> * +"job" -> "I am the master of the hall of the ancients." +"name" -> "My name is Isimov Dustbearer, Son of Fire and Earth, from the Molten Rock." +"hall","ancients" -> "The burial chamber of our ancestors. All of the firstborn of our race are there .. all but Durin." +"tibia" -> "A world of dangers, a world of wonders." +"kazordoon" -> "The last haven for dwarfenkind. Our last hope for our survival." +"big","old" -> "This mountain is as old as the world. The first dwarfs were born here." +"elves" -> "Bah, squirrels, hedgehogs, rabbits, elves ... who cares." +"humans" -> "A young and greedy race, though more noble. They remind me of orcs sometimes." +"orcs" -> "The greenskins are after all of us. Beware! Beware!" +"minotaurs" -> "They leave us alone, we leave them alone, that's the way of our people." +"pyromancer" -> "They whorship the elemental forces of fire." +"geomancer" -> "They whorship the elemental forces of earth." +"technomancer" -> "Strange, are they still around? Well, give them one or two hundred years and they are gone again, jawoll." +"god" -> "The gods abandoned us, we abandoned the gods. That's it, no big deal I tell ya, jawoll." +"keeper" -> * +"shepherd" -> * +"fire" -> "It's warm, it's useful, we use it. We decide when or how, jawoll." +"earth" -> "Gives us food and shelter, quite useful isn't it?" +"durin" -> "Ah yes, the first born. He became a higher entity to protect us. His mortal remains were buried at a remote spot where only pilgrims disturb their peace now and then." +"life" -> "Life is easy to understand, you have birth, you have death. Simple stuff, even elves could grasp the concept." +"plant" -> "Don't know much about this stuff. Find them in a soup sometimes." +"citizen" -> "You can become citizen of Kazordoon by the power of our ancestors." +"kroox" -> "What a hasty fellow. Can't be healthy to live in such a hurry, jawoll." +"jimbin" -> "Isn't that the kid that took over the Jolly Axeman tavern? Far too young for such a job, but did anyone ask me? No!" +"maryza" -> "How could she marry this Jimbin? I mean, they are kids! Know nothing about life and stuff. Couldn't they wait at least hundred years or so?" +"bezil" -> "Bezil and Nezil are typical profiteers. Fine new breed of dwarfs we raised, pah." +"nezil" -> * +"uzgod" -> "Has hardly a beard and already forgotten the traditions of his ancestors. Modern techniques ... almost like one of these technomancers." +"etzel" -> "Etzel, good old Etzel. I was his tutor long ago. Now he's running a guild ... they grow so fast ... so fast, jawoll." +"gregor" -> "Never heared that word. What's a gregor?" +"duria" -> "Could become a great warrior one day. Still needs to learn so much." +"emperor" -> "The emperor resides far above us in the upper caves. Sometimes I wonder if it's good that the emperor is that much away from the temples." +"kruzak" -> * +"motos" -> "Must admit there were worse generals in the years before, jawoll." +"general" -> * +"army" -> " A bunch of kids playing war. May the elements help us." +"ferumbras" -> "I have seen many of his type coming and going. He will fall and anotherone will take his place." +"excalibug" -> "Ahhh! Whan I was a little dwarf I was on a quest to find it. I was almost literally digging up the ghostlands for it and now only one thing is sure: It must be elsewere, jawoll." +"news" -> "News? I heard there's a new human settlement in the south, called Thai...something." +"monster" -> "Only another nuisance." +"help" -> "Can't you kids do anything on your own?" +"quest" -> "You are too young for quests, jawoll." +"task" -> * +"what","do" -> * +"gold" -> "Greed for gold could blind your sight for the important." +"money" -> * +"equipment" -> "Go and buy some." +"fight" -> "The life ot a dwarf is an eternal struggle. It hardens us and makes us the fine race we are, jawoll." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(14) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "Let me see what I can do, kid.", HP=40, EffectOpp(13) +"heal$" -> "Stop the whining, kid, that are only some scratches. Dwarfenheart knows no pain." +"time" -> "I think it's the fourth age of the yellow flame, isn't it?" +} diff --git a/data/npc/iwan.npc b/data/npc/iwan.npc new file mode 100644 index 0000000..5fb5e6d --- /dev/null +++ b/data/npc/iwan.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# iwan.npc: Datenbank für den Edelsteinhändler Iwan + +Name = "Iwan" +Outfit = (128,0-112-88-113) +Home = [33217,31833,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am busy here, %N. Wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"name" -> "I'm Iwan." +"job" -> "I sell gems of all kind." +"time" -> "It should be %T right now." +"king" -> "I am sure even the king would appreciate my wares." +"tibianus" -> * +"army" -> "I know not much about the local army." +"ferumbras" -> "I hope the academy is safe from his assaults." +"excalibug" -> "I am sure you'd easily recognize it by the gems attached to it." +"thais" -> "We supply Thais with gems found on this isle." +"tibia" -> "I know only so little about our world. It's a pity." +"carlin" -> "I never visited that city." +"edron" -> "Our island is rich in precious stones." +"news" -> "I haven't been told anything of interest lately." +"rumors" -> * + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"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." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/iwar.npc b/data/npc/iwar.npc new file mode 100644 index 0000000..633b849 --- /dev/null +++ b/data/npc/iwar.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# iwar.npc: Möbelverkäufer Iwar in Kazordoon + +Name = "Iwar" +Outfit = (160,58-108-63-76) +Home = [32618,31917,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N! Welcome to Kazordoon Furniture Store." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, %N, me busy.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Guut bye." + +"bye" -> "Guut bye.", Idle +"farewell" -> * +"name" -> "Me is Iwar Woodpecker, son of Earth, from the Savage Axes. Me run this store." +"job" -> "You moving to new home? Me specialist for equipping it." +"time" -> "Time is %T. You needing clock for your house?" +"news" -> "You meaning my specials, eh?" + +"offer" -> "Me selling statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers permanently extraordinary cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/jakahr.npc b/data/npc/jakahr.npc new file mode 100644 index 0000000..0d41ffc --- /dev/null +++ b/data/npc/jakahr.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jakahr.npc: Datenbank für den Postmann Jakahr + +Name = "Jakahr" +Outfit = (133,95-37-32-40) +Home = [33066,32876,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, mourned pilgrim. How may I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am already talking to a customer, %N. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was an honour to serve you, %N." + +"bye" -> "It was an honour to serve you.", Idle +"farewell" -> * + + +"kevin" -> "Even in our lands the name of the guildmaster is held in great respect." +"postner" -> * +"postmasters","guild" -> "The guild plays a vital role in the economy of the known world." +"join" -> "Please travel to our headquarters if you wish to join our guild." +"headquarters" -> "You can find them to the south of the dwarven city of Kazordoon." + +"job" -> "I am a member of the Postmasters Guild. If you have questions about the Royal Tibia Mail System or the depots, ask me." +"office" -> "I am always here in my office. You are welcome to visit me anytime." +"name" -> "My name is Jakahr." +"time" -> "The time is %T, pilgrim." +#"mail" -> "Our mail system is perfectly unique, and everybody is free to use it. Would you like to know more about it?", Topic=1 +"depot" -> "The depots are easy to use. Just open a locker to find your items there." + +"excalibug" -> "A weapon of legend. We rarely hear stories about it around here, however." +"news" -> "It does not befit a member of my position to spread rumours and stories, pilgrim." +"thais" -> "Thais is the capital of a kingdom on a far-off continent." +"carlin" -> "Carlin is a city far, far away from here. They say it is run by women and druids." + +@"gen-post.ndb" + +"darama" -> "On this continent, the only place of real importance is our city." +"darashia" -> "A minor settlement to the north." +"daraman" -> "As far as I can tell he was some philosopher." +"ankrahmun" -> "This city is a safe haven that protects its citizens from the dangers of the desert." +"city" -> * + +"pharaoh" -> "The pharaoh keeps this city safe. He is both our political and our spiritual leader." +"arkhothep" -> * + +"ascension" -> "Sorry, but you should discuss religous issues like these in the temple. I am not a priest, and there is little I can tell you about it." +"Akh'rah","Uthun" -> * +"Akh" -> * +"Rah" -> * +"uthun" -> * + +"arena" -> "Fights are frequently staged in the arena to entertain the people." +"palace" -> "You can't miss the palace. It is probably the biggest pyramid in the whole world." +"temple" -> "The temple is to the east of the city." + + +#"letter" -> Amount=1, Price=5, "Would you like to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Would you like to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Tibia Mail System allows you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you do not have enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you do not have enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/james.npc b/data/npc/james.npc new file mode 100644 index 0000000..b041533 --- /dev/null +++ b/data/npc/james.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# james.npc: Datenbank für den Bauern James auf Edron + +Name = "James" +Outfit = (128,115-41-45-118) +Home = [33280,31771,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Talking right now, %N. Wait a second.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"how","are","you" -> "Fine, thanks." +"sell" -> "I sell some food." +"job" -> "I am a humble farmer." +"name" -> "James." +"time" -> "I am too poor to afford a watch." +"help" -> "Sorry, can't offer you any help." +"monster" -> "There are dangerous monsters and renegade knights in the northwest behind the river and the mountain." +"dungeon" -> "I stay away from dungeons as far as I can." +"god" -> "May Crunor bless our harvests." +"king" -> "I never saw him in person." +"daniel" -> "A brave warrior as far as a farmer like me can tell." +"avar$" -> "He scares me a little." +"academy" -> "The mages and druids have quite an appetite. They buy much from me and summon even more food." +"magic" -> "I am nothing but a humble farmer and know nothing about that." +"weapon" -> * +"spell" -> * +"tibia" -> "If I were you, I would stay here." +"thais" -> "I was born in Thais, but my family moved to Edron among the first settlers." +"edron" -> * + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 + + +"buy" -> "I can offer you bread, cheese, ham, meat, and apples." +"food" -> "Are you looking for food? I have bread, cheese, ham, meat, and apples." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/jeanclaude.npc b/data/npc/jeanclaude.npc new file mode 100644 index 0000000..593850e --- /dev/null +++ b/data/npc/jeanclaude.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jeanclaude.npc: Datenbank für eine Stadtwache in Venore + +Name = "Jean Claude" +Outfit = (131,113-113-113-115) +Home = [33006,32053,6] +Radius = 5 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/jezzara.npc b/data/npc/jezzara.npc new file mode 100644 index 0000000..724f38d --- /dev/null +++ b/data/npc/jezzara.npc @@ -0,0 +1,108 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jezzara.npc: Datenbank für die pyramidenhändlerin Jezzara + +Name = "Jezzara" +Outfit = (138,3-43-91-97) +Home = [33126,32821,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell food of various kinds." +"name" -> "I am the mourned Jezzara." +"time" -> "You can buy watches here in the markethall." +"temple" -> "The temple can be found in the northeast of the city." +"pharaoh" -> "The pharaoh is our godking and the founder of our religion." +"oldpharaoh" -> "He was entombed in undead state. It is said that this will finally teach him to to strive for ascension." +"scarab" -> "I am not afraid of something that attacks only my physical form. But they stay away from the city anyway." +"chosen" -> "The chosen are those who are granted undeath after a life of service to the pharaoh." +"tibia" -> "The world can be a dangerous place for the whole of the Akh'rah Uthun." +"carlin" -> "The cities of the Tibian continent have little contact with us." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves are not really fond of the endless sands of the desert. I must say I can't blame them for it." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves don't like this land too much so we have little contact with them." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is very diverse. There are deserts and mountains as well as a large jungle region." +"darashia" -> "We are usually not allowed to travel that far without explicit permission, so we know this city mostly from the tales of travellers." +"daraman" -> "I know little of his teachings. The priests say his conclusions were inconsequent." +"ankrahmun" -> "This city is a safe haven that gives shelter from the dangers of the desert." + +"mortality" -> "The priests teach that mortality is a curse. I find it hard to understand but the priests will know best." +"false", "gods" -> "As far as I understand the gods worshipped by other nations are nothing but imposters." + +"ascension" -> "Ascension is difficult to achive. Too difficult to achieve as long as you are still alive." +"Akh'rah","Uthun" -> "Well its just the Akh, the Rah and the Unthun." +"Akh" -> "That is the body." + +"undead" -> "Those who follow the pharaoh might become undead one day." +"undeath" -> * +"Rah" -> "The Rah is the spiritual part of a being." +"uthun" -> "The Uthun is the sentient part of all living things." +"mourn" -> "The priests say we are to be mourned while we are still alive." + +"arena" -> "Sometimes spectacular battles are fought in the local arena." +"palace" -> "The palace is where the mighty pharaoh resides." + + +"buy" -> "I can offer you meat, ham, salmon, fish, fruits and vegetables." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, pumpkins and melons. What do you want?" +"vegetable" -> "I have carrots and tomatoes. What do you want?" + +"dragon","ham" -> Type=3583, Amount=1, Price=25, "Do you want to buy a dragon ham for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=10, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=15, "Do you want to buy a ham for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=7, "Do you want to buy a salmon for %P gold?", Topic=1 +"fish" -> Type=3578, Amount=1, Price=6, "Do you want to buy a fish for %P gold?", Topic=1 + + +%1,1<%1,"dragon","ham" -> Type=3583, Amount=%1, Price=25*%1, "Do you wanna buy %A dragon ham for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=10*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=15*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=7*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=6*%1, "Do you want to buy %A fish for %P gold?", Topic=1 +"fruit" -> "I have oranges, bananas, grapes, and melons. What do you want?" +"vegetable" -> "I have carrots and tomatoes. What do you want?" + +"orange" -> Type=3586, Amount=1, Price=9, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=5, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=8, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=13, "Do you want to buy a melon for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=8, "Do you want to buy a carrot for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=10, "Do you want to buy a tomato for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + + +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=9*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=5*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=8*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=13*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=8*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=10*%1, "Do you want to buy %A tomatoes for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +} diff --git a/data/npc/jimbin.npc b/data/npc/jimbin.npc new file mode 100644 index 0000000..9b0f2a8 --- /dev/null +++ b/data/npc/jimbin.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# jimbin.npc: Datenbank für den Wirt Jimbin + +Name = "Jimbin" +Outfit = (160,97-69-58-76) +Home = [32636,31886,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","jimbin",! -> "Welcome to the Jolly Axeman, have a good time, %N!" +ADDRESS,"hi$","jimbin",! -> * +ADDRESS,"hello$",! -> "Talking to me, %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","jimbin",! -> "Gimme a minute, %N.", Queue +BUSY,"hi$","jimbin",! -> * +BUSY,"hello$",! -> "Talking to me, %N?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back if you enjoyed my tavern, if not ... well, get eaten by a dragon, jawoll." + +"bye" -> "Come back if you enjoyed my tavern, if not ... well, get eaten by a dragon, jawoll.", Idle +"farewell" -> * +"hello$","maryza",! -> * +"hi$","maryza",! -> * +"job" -> "I'm runing the Jolly Axeman together with my wife Maryza." +"tavern" -> * +"maryza" -> "She's a fine cook; likes it bloddy, though. Humans call her Bloody Mary, but don't mention that to her if you're smart." +"name" -> "I am Jimbin Luckythroat, son of Earth, from the Molten Rock." +"time" -> "It is about %T." +"king" -> "The king orders huge amounts of mushroombeer for festivities." +"army" -> "I supply the army with dwarfish beer to keep morals high." +"ferumbras" -> "Hah! He never dares to trespass our realm." +"general" -> "The general is a fine man. Can drink as much as he wants and still is sober." +"excalibug" -> "Actually I belive it's more than a taverntale." +"thais" -> "Bah! Humans, can't stand a drink, jawoll." +"tibia" -> "The Tibia our race was born into was even more fierce than the world you young ones know." +"carlin" -> "Silly town. Alcohol is forbidden there and elves visit this town quite often, what certainly suggests nothing good about a town, jawoll." +"news" -> "Oh well, many hidden places of ancient times appear seemingly out of nowhere in these times." +"rumour" -> * +"rumor" -> * +"book" -> "The cookbook? It belongs to maryza. I think she has a few copies for sale." +"cookbook" -> * + +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"buy" -> "I can offer you beer ... or water if you are sick." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Ask my wife Maryza for food." +} diff --git a/data/npc/julian.npc b/data/npc/julian.npc new file mode 100644 index 0000000..4441178 --- /dev/null +++ b/data/npc/julian.npc @@ -0,0 +1,48 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# julian.npc: Datenbank für den Musikhändler Julian + +Name = "Julian" +Outfit = (128,55-30-23-115) +Home = [32883,32076,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, %N! May I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am busy right now, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I make instruments and sometimes I'm wandering through the lands of Tibia as a bard." +"name",male -> "My name is Julian, sire." +"name",female -> "My name is Julian, my lady." +"time" -> "Sorry, I don't know what time it is." +"music" -> "Music is the food of love." +"bard" -> "Bards from all over the world come here to buy their instruments." + +"offer" -> "Here you can buy lyres, lutes, drums, and simple fanfares. I also have a piano and a harp." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"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 + +%1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyres for %P gold?", Topic=1 +%1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lutes for %P gold?", Topic=1 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drums for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfares for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You need some more money." +Topic=1 -> "Oh well, next time perhaps." + +@"gen-t-furniture-instruments-s.ndb" +} diff --git a/data/npc/karl.npc b/data/npc/karl.npc new file mode 100644 index 0000000..9152c6d --- /dev/null +++ b/data/npc/karl.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# karl.npc: Datenbank fuer den Wirt der vebotenen Kneipe in Carlin + +Name = "Karl" +Outfit = (128,58-68-109-131) +Home = [32318,31797,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Pshhhht! Not that loud ... but welcome." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come back, but don't tell others." + +"bye" -> "Please come back, but don't tell others.", Idle +"farewell" -> * +"job" -> "I am the responsible for our ... uhm ... resistance." +"saloon" -> * +"resistance" -> "We fight the opression of the males and male needs by the women. This is our secret headquarters." +"headquarters" -> "Well its more a hidden tavern, so to say." +"tavern" -> "Our offers are limited but here a man can buy what a man needs." +"name" -> "I won't tell you my name." +"karl" -> "Who told you that???" +"queen" -> "Well, shes not that bad ... but some of her laws are." +"eloise" -> * +"laws" -> "Those crazy women forbid us alcohol in the city! Imagine that!" +"needs" -> * +"opression" -> * +"alcohol" -> * +"army" -> "They are the tools of opression. Hunting down every alcohol smuggler they can get." +"smuggler" -> "We collected money and hired one of the best smuggler in the whole land. His name is Todd." +"Todd" -> "A true fighter for malehood. He will bring us all the hard stuff from Thais and even contact the king there to support us." +"king" -> "I'm sure if the king learns about our tragedy, he will support us with alcohol." +"hard", "stuff" -> "Todd took all the money we could gather to buy us the best stuff on the whole continent." +"hugo" -> "I think Todd mentioned a Hugo once." +"news" -> "Some travelers from Edron told about a great treasure guarded by cruel demons in the dungeons there." +"rumors" -> * +"beer" -> Type=2880, Data=3, Amount=1, Price=20, "Do you want to buy a mug of beer for %P gold?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> "Here. Don't take it into the city though.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, come back with more gold." +Topic=1 -> "Maybe later." + +"buy" -> "I can offer you beer. For wine and realy hard stuff we have to wait for Todd." +"offers" -> * +"do","you","sell" -> * +"do","you","have" -> * + +} diff --git a/data/npc/kasmir.npc b/data/npc/kasmir.npc new file mode 100644 index 0000000..e7097ee --- /dev/null +++ b/data/npc/kasmir.npc @@ -0,0 +1,138 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Kasmir.npc: Datenbank für den Priester Kasmir + +Name = "Kasmir" +Outfit = (130,76-0-38-95) +Home = [33212,32452,1] +Radius = 3 + +Behaviour = { +ADDRESS,male,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,female,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,"hello$",! -> "May Daraman enlighten you %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "May Daraman fulfill you with patience, %N. Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Gone like a Djinn..." + +"bye" -> "Good bye, %N. May Daraman's all-seeing eye watch your travels!", Idle +"job" -> "I am a chosen of Daraman." +"news" -> "Don't look for news when you don't know the past." +"name" -> "Kasmir Ibn Darasir." +"tibia" -> "The world is only a portal the gods created to allow our ascension to heaven." +"ascension" -> "Daraman had a vision that all mortals are able to ascend to heaven, becoming celestial beings." +"heaven" -> * +"celestial" -> "By enhancing one's soul a mortal can ascend to heaven. If you are not prepared to ascend, you are bound to this world by reincarnation." +"reincarnation" -> "If your soul is not strong and purified, you will not ascend but return to life on death, even losing strength in the process." +"necromancer" -> "Undeath is even worse than reincarnation. Those souls are nothing but a rotting mockery of a soul on the path of ascension." +"daraman" -> "Daraman travelled the world and learned the secrets of the ancients. At last he learned the secret of ascension and founded his philosophy." +"soul" -> "The soul was made by the gods and therefore is divine. So by enhancing its divinity it can become more like the image of its creators." +"philosophy" -> "Daraman led his followers to this promised land to follow his teachings. It was named Darama after him later." +"darama" -> "This land is harsh and challenging. It's far away from temptations and delusions. Here Daraman's people can concentrate on themselves." +"how","are","you"-> "Thank you, I'm fine and my soul is strong." +"sell" -> "Go to the bazaar if you are interested in worldly wealth." +"sin$" -> "Do you whish to confess your sins?", Topic=3 +"sins$" -> * +"god$" -> "The gods are in heaven and far away. You are here. So concentrate on your soul and take care for it on your own." +"gods$" -> * +"life" -> "Life is divine though not without flaws." +"citizen" -> "The people are too concerned about the illusions of the moment and care less and less about Daraman's philosophy." +"caliph" -> "The caliph is heavily involved in the affairs in the world, but one has to make this sacrifice for the welfare of all." +"monster" -> "They misuse their god given souls for evil and must be destroyed." +"quest" -> "Your quest should be to prepare your soul for ascension." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "Fight is unavoidable sometimes. Make sure only your body is hurt but not your soul." +"slay" -> * + +"heal$",HP<40 -> "You are hurt, pilgrim. I will heal your wounds.", HP=40, EffectOpp(13) +"heal$",Poison>0 -> "You are poisoned, pilgrim. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",Burning>0 -> "You are burning, pilgrim. I will help you.", Burning(0,0), EffectOpp(15) +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"help$",HP<40 -> "You are hurt, pilgrim. I will heal your wounds.", HP=40, EffectOpp(13) +"help$",Poison>0 -> "You are poisoned, pilgrim. I will help you.", Poison(0,0), EffectOpp(14) +"help$",Burning>0 -> "You are burning, pilgrim. I will help you.", Burning(0,0), EffectOpp(15) +"help$" -> "You aren't looking so bad. Sorry, I need my powers for cases more severe than yours." + +"ferumbras" -> "His soul is corrupted beyond any hope for ascension." +"time" -> "Now, it is %T." +"excalibug" -> "Your greed for such items can easily corrupt your soul." +"fardos" -> "Fardos is the creator. It was his work that we possess divine souls." +"uman" -> "Uman is the positive aspect of magic. His powers flow through each Tibian, making us his children." +"suon" -> "Suon is the sun. He watches our ascension." +"crunor" -> "Crunor is the force of life and part of our all being." +"nornur" -> "Nornur is the mysterious god of fate. Daraman taught us that he is the judge who allows ascension." +"bastesh" -> "Bastesh is the goddess of the seas, and deep as the see is our soul, indeed." +"kirok" -> "Kirok is called the mad one. He gifted us with the creativity to achive our ascension." +"toth" -> "Toth is the final judge. The unworthy are condemened to reincarnation." +"banor" -> "Banor is the very proof that ascension is possible." +"tibiasula" -> "Though this entity is dead, Tibiasula's energy is present in all of us." +"tibia" -> "Tibia is the raw force of earth." +"sula" -> "Sula is the raw force of water." +"air" -> "Air is without true mind and meaning." +"fire" -> "Fire is without true mind and meaning." +"zathroth" -> "Zathroth is the corruptor of souls who does not want mortals to ascend and become more like him." +"fafnar" -> "Fafnar is a test for our endurance and dilligence." +"brog" -> "Brog's hot blood is in our veins, tempting us and distracting us from improvement of our souls." +"urgith" -> "The bonemaster is strong in the ruins of Drefia. There you can test the braveness of your soul ... or lose it to his minions." +"archdemons" -> "They are soulless and therefore without true power in the end." +"ruthless","seven" -> * + +Topic=1,"yes",CountMoney>=Price -> "May Daraman guide your quest for ascension.", DeleteMoney, EffectOpp(15) +Topic=1,"yes" -> "Don't be ashamed, but you lack the gold." +Topic=1,"no" -> "As you wish." + +Topic=3,"yes" -> "So what does trouble your soul, pilgrim?", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and try harder to improve soul." + +"marriage" -> "You want me to initiate a marriage ceremony?", Topic=5 +"ceremony" -> * +Topic=5,"yes" -> "In the Name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time. Marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence please! I hereby invoke the attention of the eternal powers looking over our souls and lives. May the gods bless us!", EffectMe(13), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,male,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further live as a married couple. Go now and celebrate your marriage!", EffectOpp(14), EffectMe(13), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", Idle +} diff --git a/data/npc/kawill.npc b/data/npc/kawill.npc new file mode 100644 index 0000000..bb43bc2 --- /dev/null +++ b/data/npc/kawill.npc @@ -0,0 +1,123 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kawill.npc: Datenbank für den Geomancer Kawill (Zwergenstadt) + +Name = "Kawill" +Outfit = (66,0-0-0-0) +Home = [32644,31969,12] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! May earth protect you!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. Let me help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. Let me help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are hurt, my child, let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Earth under your feet, pilgrim." + +"bye" -> "Earth under your feet, %N!", Idle +"farewell" -> * + +"fire",PvPEnforced,! -> "The lifeforce of this world is wanig. There are no more blessings avaliable on this world." +"suns",PvPEnforced,! -> * +"fire","suns" -> "You can get the blessing of the two suns in the suntower near Ab'Dendriel." + +"job" -> "I am the great geomancer of dwarvenkind." +"name" -> "I am Kawill Marbleeye, Son of Earth, from the Molten Rock." +"tibia" -> "Nice world in general. It's a shame there is so much water ruining the general impression." +"kazordoon" -> "By using the powers of fire and earth we forced the river that once wound its way through the big old one in other directions, and created our home." +"big","old" -> "The mountain we live in is called the big old one. It's the mountain of mountains, and it isand like a friend and protector to our race." +"elves" -> "Who cares for that silly people." +"humans" -> "We are allied with this young race, though they seldom have the wisdom to listen to us." +"orcs" -> "Stupid beasts. Their savagery is only rivalled by their smell." +"minotaurs" -> "They lost their way long ago. Now they are lost and doomed. It should be a warning to all of us." +"geomancer" -> "We investigate the will of the earth. It is our duty to make sure things to work in their natural way." +"god" -> "The gods are treacherous and vain. They want to use us like they did in the past. Only the elements can be trusted, because all they want is for nature to run its set course." +"earth" -> "The lifegiving earth protects us, feeds us and takes us home after death." +"fire" -> "Where earth is giving, fire is taking. That is the way of the elements." +"life" -> "Life is born by earth and fed by earth." +"plant" -> "Plants are minor messengers of earth. If you understand the soil you understand the plants." +"citizen" -> "Many people are living in the embracement of earth in Kazordoon." +"kroox" -> "He is a fine smith and his armour may save your neck one day." +"jimbin" -> "He is a jolly fellow and one of the oldest dwarves alive." +"maryza" -> "She is a fine cook, jawoll." +"bezil" -> "Bezil and Nezil have pawn and equpiment shop with an amazing stock." +"nezil" -> * +"uzgod" -> "Uzgod is a blacksmith and understands the ways of his element well." +"etzel" -> "I fear the sorcerers focus on the destructive forces of fire. They forget about the protection earth could provide." +"motos" -> "The scars in this dwarf's face tell the tale of many a great battle." +"durin" -> "The celestial paladin, the protector of our race. The only divine being we care for and the only one who still cares for dwarfs." +"duria" -> "The first knight of dwarvenkind is a fine woman." +"emperor" -> "The emperor has rarely visited the temple district in the last years. He should care more about spirituality then about politics. Jawoll." +"kruzak" -> * +"pyromancer" -> "They are the followers of the great flame." +"technomancer" -> "FOOLS! FOOLS! ALL OF THEM! MAY EARTH SWALLOW THEM ALL!" +"motos" -> "He finally made his peace with his own heart." +"general" -> * +"army" -> "Our fortresses are strong and easy to defend. Any aggressor will be smashed by our armies. Most intruders will not get manage to pass the colossus." +"colossus" -> "The big fortress that guards our realm is shaped like a dwarf." +"ferumbras" -> "The day will come when he finally bites the dust." +"excalibug" -> "Ah, a weapon to be feared by man, beast and god alike, jawoll. He who wields it will be both blessed and cursed at the same time." +"news" -> "There will be nothing new, but every pain and every pleasure will return to you, and all in the same order. The eternal hour glass of existence will be turned again and again, and you will be turned with it, little speck of dust." +"Nietzsche" -> "In his mind he might have been a giant, but in his heart he was a dwarf." +"monster" -> "May the earth swallow them all!" +"stone","golem" -> "These beings are filled with the power of earth. Therefore they, too, are sacred in a way." +"help" -> "I am a mere diviner of earth's will and not allowed to help you." +"quest" -> "There's nothing I need, better ask others." +"task" -> * +"what","do" -> * +"gold" -> "Gold is one of earth's treasures. To have gold is to be blessed by earth. To be rich is to be favoured by earth." +"money" -> * +"equipment" -> "You can buy equiment in Bezil's and Nezil's little shop." +"fight" -> "Never forget your defence when fighting." + +"heal$",Burning>0 -> "You are burning. Let me help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. Let me help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are hurt, my child. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you recive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "You can receive the spiritual shielding which in the whiteflower temple south of thais." +"shielding" -> * +"spark" -> "The spark of the phoenix is given by me and by the great pyromancer in the nearby firetemple. Do you wish to receive my part of the blessing of the phoenix?", topic=1 +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." +# "fire","suns" -> "You can get the blessing of the two suns in the suntower near Ab'Dendriel." +# "suns" -> * +# nach oben gestellt wg antwort auf fire +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=1,"yes", QuestValue(199) > 0,! -> "You already possess my blessing." +Topic=1,"yes", QuestValue(102) > 0,! -> "You already possess my blessing." +Topic=1,"yes" -> "So receive the blessing of the live-giving earth, pilgrim.", EffectOpp(13),SetQuestValue(199,1) +Topic=1 -> "Ok. If you don't want it ... ." + + +"time" -> "Time is not of importance." +} diff --git a/data/npc/kazzan.npc b/data/npc/kazzan.npc new file mode 100644 index 0000000..e176cdf --- /dev/null +++ b/data/npc/kazzan.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kazzan.npc: Datenbank für Kalif Kazzan + +Name = "Kazzan" +Outfit = (130,95-13-15-76) +Home = [33231,32389,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome in the lands of the children of the enlightened Daraman, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,"salutations$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will test your patience %N, since I am already talking!", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your soul flourish" + +"bye" -> "May your soul flourish.", Idle +"news" -> "I don't give much attention to rumours." +"king" -> "Tibianus is the shepherd of the lost souls of the so called Thaian empire." +"tibianus" -> * +"thais" -> * +"name" -> "I am Kazzan Ibn Gadral, caliph of Darama." +"job" -> "I am the caliph of the children of Daraman." +"caliph" -> "A caliph is a leader of his people, just like a king." +"how","are","you"-> "I am fine, thank you." +"army" -> "Our people are well prepared to fight for their land and their souls." +"guard" -> * +"enemies" -> "The necromancers of Drefia fell under the wrath of the djinns once. If they challenge us again they might lose more than a city." +"enemy" -> * +"drefia" -> "When the djinns destroyed the better part of the unholy town in their wrath, the brotherhood hid like worms in the sand." +"brotherhood" -> "The Brotherhood of Bones came here fleeing some war on the continent. They corrupted the settlers of the Thaian colony with ease." +"minotaur" -> "The minotaurs are another test we have to endure. They inhabit the pyramid which is taboo for our people, as Daraman taught us." +"daraman" -> "Daraman led our ancestors to the continent Darashia to live a life of simplicity and meditation." +"taboo" -> "Daraman knew our souls might get corupted by the things hidden there." +"quest" -> "I will not entrust foreigners with any quest. Live amongst us for some years and listen to Daraman's teachings and we will see." +"mission" -> * +"god" -> "The gods are powerful but it's ultimately up to us to work on our souls' ascension." +"excalibug" -> "Once a djinn claimed to have seen it in a dream. I guess it's just that, some dream of a supernatural creature." +"kazordoon" -> "We have lost contact with the dwarf people of Kazordoon." +"dwarf" -> * +"carlin" -> "We keep in touch with the queen but did not take sides in the conflict of Carlin and Thais ... yet." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/data/npc/kevin.npc b/data/npc/kevin.npc new file mode 100644 index 0000000..7faf32b --- /dev/null +++ b/data/npc/kevin.npc @@ -0,0 +1,164 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kevin.npc: Datenbank für Kevin Postner den Anführer der Postlergilde + +Name = "Kevin" +Outfit = (128,76-43-38-76) +Home = [32569,32018,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, what brings you here?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +# TOPICS BIS EINSCHL. 25 BENUTZT + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"name" -> "My name is Kevin. Kevin Postner, that is." +"job" -> "I am the head of the Tibian Postmaster's Guild." +"guild" -> "We are a powerful organisation that is vital part of Tibian society. It's a honour and a privilege to be a member." +"postmaster" -> * +"academy" -> "Ah, yes! One day I will found a postofficers academy. Perhaps with the help of our most able members. But rhats a task for another day." +"wally" -> "Wally is my right hand, so to say. He is the head to our guilds office branch." +"branch" -> "Well, thers the depot branch, the office brance, the delivery branch and some officers for special operations." +"special","operations",QuestValue(250)<4 -> "Sorry but I won't talk about this matter with someone of your rank." +"special","operations",QuestValue(250)>3 -> "We have a secret branch, called 'the stamps of the gods'. They secretly supervise our members reliability and have an eye on certain groups of interest that want to gain influence over our guild." + +"member",QuestValue(227)<1 -> "We have high standards for our members. To rise in our guild is a difficult but rewarding task. Why do you ask? Are you interested in joining?",Topic=1 +"member",QuestValue(227)>0 -> "You are already a member, %N." +"mission",QuestValue(227)<1 -> "You are not a member of our guild yet! We have high standards for our members. To rise in our guild is a difficult but rewarding task. Are you interested in joining?",Topic=1 + +"advancement",QuestValue(250) "You are worthy indeed. Do you want to advance in our guild?",Topic=23 +"advancement",QuestValue(250)>=QuestValue(249) -> "Sorry, but you are not yet ready for advancement." +"yes",Topic=23,QuestValue(249)=2 -> "I grant you the title of postman. You are now a full member of our guild. Here have your own officers hat and wear it with pride.",SetQuestValue(250,2), Amount=1, Create(3576) +"yes",Topic=23,QuestValue(249)=3 -> "From now on it shall be known that you are a grand postman. You are now a privileged member until the end of days. Most captains around the world have an agreement with our guild to transport our privileged members, like you, for less gold.",SetQuestValue(250,3) +"yes",Topic=23,QuestValue(249)=4 -> "From now on you are a grand postman for special operations. You are an honoured member of our guild and earned the privilege of your own post horn. Here, take it.",SetQuestValue(250,4), Amount=1, Create(2957) +"yes",Topic=23,QuestValue(249)=5 -> "I grant you the title of archpostman. You are a legend in our guild. As privilege of your newly aquired status you are allowed to make use of certain mailboxes in dangerous areas. Just look out for them and you'll see.",SetQuestValue(250,5) + +"mission",QuestValue(248)=1,QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." + +"mission",QuestValue(248)=1 -> "You have mastered all our current missions. There's nothing left to be done ... for now." + +"mission",QuestValue(245)=1 -> "You are not done with your current mission. Deliver that letter to king Markwin. Please report back when you are ready." +"mission",QuestValue(245)=2 -> "You have delivered that letter? You are a true postofficer. All over the land bards shall praise your name. There are no missions for you left right now.",SetQuestValue(248,1),SetQuestValue(249,5) + +"mission",QuestValue(244)=1 -> "You are not done with your current mission. Deliver those letters to Santa. Please report back when you are ready." +"mission",QuestValue(244)=2 -> "You did it? I hope you did not catch a flu in the cold! However theres another mission for you. Are you interested?",topic=21 +Topic=21,"yes" -> "Excellent. Here is a letter for you to deliver. Well, to be honest, no one else volunteered. It's a letter from the mother of Markwin, the king of Mintwallin. Deliver that letter to him, but note that you will not be welcome there.",SetQuestValue(245,1), Amount=1, Create(3220) +Topic=21 -> "Too bad, perhaps another time then." + +"mission",QuestValue(246)=1,QuestValue(244)=0 -> "So are you ready for another Mission?", Topic=20 + +"mission",QuestValue(243)=0,QuestValue(242)=1 -> "You are not done with your current mission. Search for the whereabout of Postofficer Waldo. Please report back when you are ready." +"mission",QuestValue(243)=1 -> "So Waldo is dead? This is grave news indeed. Did you recover his posthorn?",topic=19,Type=3219, Amount=1 +Topic=19,"yes",Count(Type)>=Amount -> "Thank you. We will honour this. Your next mission will be a very special one. Good thing you are a special person as well. Are you ready?",Delete(Type),SetQuestValue(246,1),SetQuestValue(249,4),Topic=20 +Topic=19,"yes" -> "Hm, no, you don't have it. Too bad, go and look for it." +Topic=19 -> "Too bad, go and look for it." + +Topic=20,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." +Topic=20,"yes" -> "So listen well. Behind the lower left door you will find a bag. The letters in the bag are for none other than Santa Claus! Deliver them to his house on the isle of Vega, USE the bag on his mailbox and report back here.",SetQuestValue(244,1) +Topic=20 -> "Too bad, perhaps another time then." + +"mission",QuestValue(234)>0,QuestValue(234)<7 -> "You are not done with your current mission. We still need the measurements of several postofficers. Please report back when you are ready." +"mission",QuestValue(234)=7 -> "Once more you have impressed me! Are you willing to do another job?",topic=17 +Topic=17,"yes" -> "Ok but your next assignment might be dangerous. Our Courier Waldo has been missing for a while. I must assume he is dead. Can you follow me so far?", topic=18 +Topic=17 -> "Too bad, perhaps another time then." + +Topic=18,"yes" -> "Find out about his whereabouts and retrieve him or at least his posthorn. He was looking for a new underground passage that is rumoured to be found underneath the troll-infested Mountain east of Thais.",SetQuestValue(242,1) +Topic=18 -> "Too bad, perhaps you will try some other time then." + +"mission",QuestValue(233)>0,QuestValue(233)<10 -> "You are not done with your current mission. Make sure Hugo chief is tailoring our new uniforms. Please report back when you are ready." +"mission",QuestValue(233)=10 -> "Excellent! Another job well done! Would you accept another mission?",SetQuestValue(249,3),topic=16 + +Topic=16,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first let's talk about advancement." +Topic=16,"yes" -> "Good, so listen. Hugo Chief informed me that he needs the measurements of our postofficers. Go and bring me the measurements of Ben, Lokur, Dove, Liane, Chrystal and Olrik.",SetQuestValue(234,1) +Topic=16 -> "Too bad, perhaps another time then." + +"dress","pattern",QuestValue(233)=8 -> "Fine, fine. I think that should do it. Tell Hugo that we order those uniforms. The completed dress pattern will soon arrive in Venore. Report to me when you have talked to him.",SetQuestValue(233,9) + +"dress","pattern",QuestValue(233)=6 -> "The queen has sent me the samples we needed. The next part is tricky. We need the uniforms to emanate some odor that dogs hate.The dog with the best 'taste' in that field is Noodles, the dog of King Tibianus. Do you understand so far?",Topic=15,Amount=Random(1,3) +"uniform",QuestValue(232)=6 -> * +Topic=15,"yes" -> "Good. Go there and find out what taste he dislikes most: moldy cheese, a piece of fur or a bananaskin. Tell him to SNIFF, then the object. Show him the object and ask 'Do you like that?'. DONT let the guards know what you are doing.",SetQuestValue(233,7),SetQuestValue(251,Amount) +Topic=15 -> "Too bad, perhaps you can try doing it some other time then." + +"dress","pattern",QuestValue(233)=2 -> "Oh yes, where did we get that from ...? Let's see, first ask the great technomancer in Kazordoon for the technical details. Return here afterwards.",SetQuestvalue(233,3) + +"dress","pattern",QuestValue(233)=4 -> "The mail with Talphion's instructions just arrived. I remember we asked Queen Eloise of Carlin for the perfect colours. Go there, ask her about the UNIFORMS and report back here.",SetQuestvalue(233,5) + +"mission",QuestValue(231)=1 -> "You are not done with your current mission. Deliver that present to Fibula. Please report back when you are ready." +"mission",QuestValue(231)=2 -> "Splendid, I knew we could trust you. I would like to ask for your help in another matter. Are you interested?",topic=14 +Topic=14,"yes" -> "Ok. We need a new set of uniforms, and only the best will do for us. Please travel to Venore and negotiate with Hugo Chief a contract for new uniforms.",SetQuestValue(233,1) +Topic=14 -> "Too bad, perhaps another time then." + +# BEFÖRDERUNG 2 +Topic=13,"yes",QuestValue(250) "Your eagerness is a virtue, young one, but first lets talk about advancement." +Topic=13,"yes" -> "Since I am convinced I can trust you, this time you must deliver a valuable present to Dermot on Fibula. Do NOT open it!!! You will find the present behind the door here on the lower right side of this room.",SetQuestValue(231,1) + +Topic=13 -> "Too bad, perhaps another time then." + +"mission",QuestValue(230)=1 -> Type=3115, Amount=1,"Do you bring ONE bone for our officers' safety fund or ALL bones at once?",topic=24 +"mission",QuestValue(230)>1,QuestValue(230)<20 -> Type=3115, Amount=1,"Do you bring a bone for our officers' safety fund?",topic=12 +"mission",QuestValue(230)>1,QuestValue(230)=20 -> Type=3115, Amount=1,"Do you bring a bone for our officers' safety fund?",topic=22 +"mission",QuestValue(230)>20 -> "You have made it! We have enough bones for the fund! You remind me of myself when I was young! Interested in another mission?",Topic=13 + +Topic=12,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "Excellent! You have collected %A bones. Just report about your mission again if you find more." +Topic=12 -> "You have no suitable bone with you. Too bad, but you surely will find some more." +Topic=22,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "You have collected all the %A bones needed. Excellent! Now let's talk about further missions if you are interested.",SetQuestValue(249,2) +Topic=22 -> "You have no suitable bone with you. Too bad, but you surely will find some more." + +Topic=24,"one",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,QuestValue(230)+1),Amount=QuestValue(230)-1, "Excellent! You have collected %A bones. Just report about your mission again if you find more." +Topic=24,"all" -> Type=3115, Amount=20,"Are you sure you have collected all the 20 bones needed?",topic=25 + +Topic=25,"yes",Count(Type)>=Amount -> Delete(Type),SetQuestValue(230,21),"You have collected all the 20 bones needed. Excellent! Now let's talk about further missions if you are interested.",SetQuestValue(249,2) +Topic=25 -> "You have not enough bones with you. Too bad, but you surely will find some more." + +"mission",QuestValue(229)>0,QuestValue(229)<3 -> "You are not done with your current mission. Find David Brassacres and hand him that bill. Report when you are ready." +"mission",QuestValue(229)=3 -> "You truly got him? Quite impressive. You are a very prommising candidate! I think I have another mission for you. Are you interested?",Topic=11 + +"mission",QuestValue(228)=1 -> "You are not done with your current mission. The mailbox is still not fixed. Report if you are ready." +"mission",QuestValue(228)=2 -> "Excellent, you got it fixed! This will teach this mailbox a lesson indeed! Are you interested in another assignment?",Topic=9 + +"mission",QuestValue(227)<5 -> "You are not done with your current mission. Make sure all the passages are secure. Report if you are ready." +"mission",QuestValue(227)=5 -> "So you have finally made it! I did not think that you would have it in you ... However: are you ready for another assignment?",Topic=8 + +Topic=11,"yes" -> "Ok, listen: we have some serious trouble with agressive dogs lately. We have accumulated some bones as a sort of pacifier but we need more. Collect 20 Bones like the one in my room to the left and report here.",SetQuestValue(230,1) +Topic=11 -> "Too bad, perhaps another time then." + +Topic=9,"yes" -> "For your noble deeds I grant you the title Assistant Postofficer. All Postofficers will charge you less money from now on. After every second mission ask me for an ADVANCEMENT. Your next task will be a bit more challenging. Do you feel ready for it?",SetQuestValue(250,1),SetQuestValue(249,1),Topic=10 +Topic=9 -> "Too bad, perhaps another time then." + +Topic=10,"yes" -> "I need you to deliver a bill to the stage magician David Brassacres. He's hiding from his creditors somewhere in Venore. It's likely you will have to trick him somehow to reveal his identity. Report back when you delivered this bill.",Amount=1,Create(3216),SetQuestValue(229,1) +Topic=10 -> "Too bad, perhaps another time then." + +# Topic=8,"yes" -> "I am glad to hear that. One of our mailboxes was reported to be jammed. It is located on the so called 'mountain' on the isle Folda. Get a crowbar and fix the mailbox. Report about your mission when you have done so.",Topic=9,SetQuestValue(228,1) +Topic=8,"yes" -> "I am glad to hear that. One of our mailboxes was reported to be jammed. It is located on the so called 'mountain' on the isle Folda. Get a crowbar and fix the mailbox. Report about your mission when you have done so.",SetQuestValue(228,1) +Topic=8 -> "I thought so. The mail service is not for just anyone." + +Topic=1,"yes" -> "Hm, I might consider your proposal, but first you will have to prove your worth by doing some tasks for us. Are you willing to do that?",Topic=2 +Topic=1 -> "I thought so. The mail service is not for just anyone." + +Topic=2,"yes" -> "Excellent! Your first task will be quite simple. But you should better write my instructions down anyways. You can read and write?",Topic=3 +Topic=2 -> "I thought so. The mail service is not for just anyone." + +Topic=3,"yes" -> "So listen, you will check certain tours our members have to take to see if there is some trouble. First travel with Captain Bluebear's ship from Thais to Carlin, understood?",Topic=4 +Topic=3 -> "I am sorry, but being illiterate disqualifies you from joining." + +Topic=4,"yes" -> "Excellent! Once you have done that you will travel with Uzon to Edron. You will find him in the Femor Hills. Understood?",Topic=5 +Topic=4 -> "I thought so. The mail service is not for just anyone." + +Topic=5,"yes" -> "Fine, fine! Next, travel with Captain Seahorse to the city of Venore. Understood?",Topic=6 +Topic=5 -> "I thought so. The mail service is not for just anyone." + +Topic=6,"yes" -> "Good! Finally, find the technomancer Brodrosch and travel with him to the Isle of Cormaya. After this passage report back to me here. Understood?",Topic=7 +Topic=6 -> "I thought so. The mail service is not for just anyyone." + +Topic=7,"yes" -> "Ok, remember: the Tibian mail service puts trust in you! Don't fail and report back soon. Just tell me about your MISSION.",SetQuestValue(227,1) +Topic=7 -> "I thought so. The mail service is not for just anyone." +} diff --git a/data/npc/king.npc b/data/npc/king.npc new file mode 100644 index 0000000..31daa9f --- /dev/null +++ b/data/npc/king.npc @@ -0,0 +1,96 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# king.npc: Datenbank für den König von Tibia + +Name = "King Tibianus" +Outfit = (130,21-87-107-95) +Home = [32311,32171,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello","king",! -> "I greet thee, my loyal subject." +ADDRESS,"hail","king",! -> * +ADDRESS,"salutations","king",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "What a lack of manners!" + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am your sovereign, King Tibianus III, and it's my duty to provide justice and guidance for my subjects." +"justice" -> "I try my best to be just and fair to our citizens. The army and the TBI are a great help for fulfilling this duty." +"name" -> "It's hard to believe that you don't know your own king!" +"news" -> "The latest news are usually brought to our magnificent town by brave adventurers. They spread tales of their journeys at Frodo's tavern." +"tibia" -> "Soon the whole land will be ruled by me once again!" +"land" -> * +"how","are","you"-> "Thank you, I'm fine." +"castle" -> "Rain Castle is my home." +"sell" -> "Sell? Sell what? My kingdom isn't for sale!" +"god" -> "Honor the gods and pay your taxes." +"zathroth" -> "Please ask a priest about the gods." +"citizen" -> "The citizens of Tibia are my subjects. Ask the old monk Quentin to learn more about them." +"sam" -> "He is a skilled blacksmith and a loyal subject." +"frodo" -> "He is the owner of Frodo's Hut and a faithful tax-payer." +"gorn" -> "He was once one of Tibia's greatest fighters. Now he is selling equipment." +"benjamin" -> "He was once my greatest general. Now he is very old and senile but we entrusted him with work for the Royal Tibia Mail." +"harkath" -> "Harkath Bloodblade is the general of our glorious army." +"bloodblade" -> * +"general" -> * +"noodles" -> "The royal poodle Noodles is my greatest treasure!" +"ferumbras" -> "He is a follower of the evil god Zathroth and responsible for many attacks on us. Kill him on sight!" +"bozo" -> "He is my royal jester and cheers me up now and then." +"treasure" -> "The royal poodle Noodles is my greatest treasure!" +"monster" -> "Go and hunt them! For king and country!" +"help" -> "Visit Quentin, the monk, for help." +"quest" -> "I will call for heroes as soon as the need arises again and then reward them appropriately." +"mission" -> * +"gold" -> "To pay your taxes, visit the royal tax collector." +"money" -> * +"tax" -> * +"sewer" -> "What a disgusting topic!" +"dungeon" -> "Dungeons are no places for kings." +"equipment" -> "Feel free to buy it in our town's fine shops." +"food" -> "Ask the royal cook for some food." +"time" -> "It's a time for heroes, that's for sure!" +"heroes" -> * +"hero$" -> * +"adventurer" -> * +"tax","collector"-> "He has been lazy lately. I bet you have not payed any taxes at all." +"king" -> "I am the king, so mind your words!" +"army" -> "Ask the soldiers about that topic." +"enemy" -> "Our enemies are numerous. The evil minotaurs, Ferumbras, and the renegade city of Carlin to the north are just some of them." +"enemies" -> * +"city","north" -> "They dare to reject my reign over the whole continent!" +"carlin" -> * +"thais" -> "Our beloved city has some fine shops, guildhouses, and a modern system of sewers." +"city" -> * +"shop" -> "Visit the shops of our merchants and craftsmen." +"merchant" -> "Ask around about them." +"craftsmen" -> * +"guild" -> "The four major guilds are the knights, the paladins, the druids, and the sorcerers." +"minotaur" -> "Vile monsters, but I must admit they are strong and sometimes even cunning ... in their own bestial way." +"paladin" -> "The paladins are great protectors for Thais." +"elane" -> * +"knight" -> "The brave knights are necessary for human survival in Thais." +"gregor" -> * +"sorcerer" -> "The magic of the sorcerers is a powerful tool to smite our enemies." +"muriel" -> * +"druid" -> "We need the druidic healing powers to fight evil." +"marvik" -> * +"good" -> "The forces of good are hard pressed in these dark times." +"evil" -> "We need all strength we can muster to smite evil!" +"order" -> "We need order to survive!" +"chaos" -> "Chaos arises from selfishness, and that's its weakness." +"excalibug" -> "It's the sword of the kings. If you could return this weapon to me I would reward you beyond your dreams." +"reward" -> "Well, if you want a reward, go on a quest to bring me Excalibug!" +"chester" -> "A very competent person. A little nervous but very competent." +"tbi$" -> "This organisation is important in holding our enemies in check. Its headquarter is located in the bastion in the northwall." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/data/npc/kroox.npc b/data/npc/kroox.npc new file mode 100644 index 0000000..461b7ea --- /dev/null +++ b/data/npc/kroox.npc @@ -0,0 +1,129 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kroox.npc: Datenbank für den Rüstungshändler Kroox + +Name = "Kroox" +Outfit = (160,0-100-105-76) +Home = [32650,31887,9] +Radius = 6 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Kroox Quality Armor, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You next %N, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * + +"measurements",QuestValue(235)>0,QuestValue(237)<1 -> "Hm, well I guess its ok to tell you ... ",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(237,1) + +"measurements" -> "UH? No clue what you are talking about, jawoll." + +"job" -> "I sell best armor in land. My armor save you life. Better buy much." +"shop" -> * +"name" -> "My name is Kroox Shieldbearer, son of Earth, from the Molten Rock." +"time" -> "It's %T now." +"help" -> "I sell and buy all kinds of armor. Dwarfish are the best, jawoll!" +"dwarf$" -> "We are proud fellows." +"monster" -> "You not be afraid, here you be save." +"dungeon" -> "Much fun you can have in dungeons. Much battle and much gold, jawoll!" +"mines" -> "Foreigners not welcome in mines. An evil basilisk rob our deeper mines." +"thanks" -> "I you thank, too." +"thank","you" -> * + +"buy" -> "What you need? I sell armor, helmets, shields, and legs." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "I offer armor, helmets, legs, and shields." +"weapon" -> "Ask in the shop next tunnel about that." +"helmet" -> "I sell chain helmets, brass helmets, iron helmets, and steel helmets. What you want?" +"armor" -> "I sell chain armor, brass armor, and plate armor. What you need?" +"shield" -> "I sell steel shields, dwarven shields, brass shields, and plate shields. What you want?" +"trousers" -> "I am selling chain legs, and brass legs. What you need?" +"legs" -> * +"you","buy" -> "You want sell any armor?" + +"chain","armor" -> Type=3358, Amount=1, Price=200, "You want buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "You want buy a brass armor for %P gold?", Topic=1 +"plate","armor" -> Type=3357, Amount=1, Price=1200,"You want buy a plate armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "You want buy a chain helmet for %P gold?", Topic=1 +"brass","helmet" -> Type=3354, Amount=1, Price=120, "You want buy a brass helmet for %P gold?", Topic=1 +"iron","helmet" -> Type=3353, Amount=1, Price=390, "You want buy an iron helmet for %P gold?", Topic=1 +"steel","helmet" -> Type=3351, Amount=1, Price=580, "You want buy a steel helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "You want buy a steel shield for %P gold?", Topic=1 +"brass","shield" -> Type=3411, Amount=1, Price=65, "You want buy a brass shield for %P gold?", Topic=1 +"plate","shield" -> Type=3410, Amount=1, Price=125, "You want buy a plate shield for %P gold?", Topic=1 +"dwarven","shield" -> Type=3425, Amount=1, Price=500, "You want buy a dwarven shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "You want buy chain legs for %P gold?", Topic=1 +"brass","legs" -> Type=3372, Amount=1, Price=195, "You want buy brass legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "You want buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "You want buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=1200*%1,"You want buy %A plate armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "You want buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=120*%1, "You want buy %A brass helmets for %P gold?", Topic=1 +%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=390*%1, "You want buy %A iron helmets for %P gold?", Topic=1 +%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=580*%1, "You want buy %A steel helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "You want buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=65*%1, "You want buy %A brass shields for %P gold?", Topic=1 +%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=125*%1, "You want buy %A plate shields for %P gold?", Topic=1 +%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=500*%1, "You want buy %A dwarven shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "You want buy %A chain legs for %P gold?", Topic=1 +%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=195*%1, "You want buy %A brass legs for %P gold?", Topic=1 + +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "You want sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=112, "You want sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=240, "You want sell a plate armor for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=293, "You want sell a steel helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "You want sell a chain helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "You want sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "You want sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "You want sell a iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "You want sell a devil's helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "You want sell a warrior helmet for %P gold?", Topic=2 +"sell","dwarven","shield" -> Type=3425, Amount=1, Price=100, "You want sell a dwarven shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=45, "You want sell a plate shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "You want sell a brass shield for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "You want sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=60, "You want sell a battle shield for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "You want sell brass legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "You want sell chain legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "You want sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "You want sell knight legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "You want sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=112*%1, "You want sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=240*%1, "You want sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=293*%1, "You want sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "You want sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "You want sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "You want sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "You want sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "You want sell %A devil's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "You want sell %A warrior helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"dwarven","shield" -> Type=3425, Amount=%1, Price=100*%1, "You want sell %A dwarven shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=45*%1, "You want sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "You want sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "You want sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=60*%1, "You want sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "You want sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "You want sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "You want sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "You want sell %A knight legs for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here, you take it.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No, no, you do not hold enough gold." +Topic=1 -> "I think you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe you sell it next time." +"sam","sen",QuestValue(289)=1 -> "Oh, so its you, he wrote me about? Sadly I have no dwarven armor in stock. But I give you the permission to retrive one from the mines. ...", + "The problem is, some giant spiders made the tunnels where the storage is their new home. Good luck.",SetQuestValue(289,2) +} diff --git a/data/npc/kruzak.npc b/data/npc/kruzak.npc new file mode 100644 index 0000000..2db7ba2 --- /dev/null +++ b/data/npc/kruzak.npc @@ -0,0 +1,93 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# kruzak.npc: Datenbank für den Zwergenkönig + +Name = "Emperor Kruzak" +Outfit = (66,0-0-0-0) +Home = [32627,31923,3] +Radius = 1 + +Behaviour = { +ADDRESS,"hello","emperor",! -> "Hiho, may Fire and Earth bless you, my child." +ADDRESS,"hail","emperor",! -> * +ADDRESS,"salutations","emperor",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,! -> "Gone, like swallowed from the earth!" + +"bye" -> "Farewell, %N, my child!", Idle +"farewell" -> * +"job" -> "Well, I am the emperor of the dwarfs. It's my duty to protect my folk and give them justice." +"justice" -> "Well, justice is a difficult thing. Can one be just to all at once, and if not, to whom should he be just?" +"name" -> "Well, I am Emperor Kruzak Dustbeard, son of Fire and Earth, second only to the gods, jawoll." +"news" -> "Well, I am too old to care about gossip anymore." +"tibia" -> "Well, the gods handed the lands over to the younger races, but my people will leave this word in dignity." +"land" -> * + +"how","are","you"-> "Well, I'm fine, the last centuries have been good to me, jawoll." +"castle" -> "Well, our people need no castle, our home IS a fortress." +"sell" -> "Well, what do you expect kings to sell?" +"god" -> "Well, we honor Father Earth and Mother Fire. Some of us even follow the teachings of additional gods." +"citizen" -> "Well, all dwarfs could be considered citizens. We are brothers and sisters in Fire and Earth." +"noodles" -> "WAAAH! Don't mention that beast! It was after the bones of our ancestors, last time King Tibianus visted us!" +"ferumbras" -> "Well, we are prepared even for him." +"treasure" -> "Well, we are not poor people, but we have ways to defend our wealth." +"monster" -> "Well, it's up to the younger ones to slay the beasts that roam the lands and the tunnels." +"help" -> "Well, I am too busy to help you, but feel free to ask around." +"quest" -> "Well, if you wander the world with open eyes, you will see the quests without asking." +"mission" -> * +"gold" -> "Well, our people love gold; that's common knowledge." +"money" -> * +"tax" -> * +"mines" -> "Well, our mines have been invaded by a basilisk. We trapped him with a cave in. The hero that could bring me the body of the beast will get a great reward." +"dungeon" -> "Well, there are a lot of dungeons in the lands, waiting to be explored by the daring ones." +"equipment" -> "Well, go and buy it in the city." +"food" -> * +"time" -> "Well, after some centuries I stopped to worry about time anymore." +"hero" -> "Well, we dwarfs produced some of the greatest heroes of all times." +"adventurer" -> * +"tax" -> "Well, our taxes are moderate." +"emperor" -> "Well, I am the emperor of the dwarfs and the oldest living dwarf, jawoll!" +"age" -> "Well, I don't want to talk about my age." +"old" -> * +"youth" -> * +"army" -> "Well, you better ask the general." +"enemy" -> "Well, only a dead enemy is a good enemy." +"enemies" -> * +"thais" -> "Well we are at peace with Carlin and Thais." +"carlin" -> * +"city" -> "Well, go and see the wonders of our cities yourself." +"shop" -> "Well, my subjects maintain many fine shops. Go and have a look at their wares." +"merchant" -> "Well, there are some in our city. Go and visit them." +"craftsmen" -> * +"guild" -> "Well, we have two guilds in our town, the Knights and the Sorcerers." +"minotaur" -> "Well, we don't fear them. They seem more occupied with the humans anyway." +"elves" -> "Well, they are not as bad as one might think." +"paladin" -> "Well, we have some crossbowmen in our army but rely more on our knights." +"legola" -> "Well, isn't that the leader of the Carlin Paladins?" +"elane" -> "Well, the High Paladin is a woman that deserves respect. I admired most Elanes I met in my life." +"knight" -> "Well, dwarfish knights are feared by our enemies in the whole world." +"trisha" -> "Well, that's the Carlin High Knight, isn't she? Not to bad as fighter for a big one." +"sorceror" -> "Well, the sorcerers are followers of the elemental powers." + +"durin" -> "Well, Durin is one of the celestial paladins, messenger of the gods. He is the protector of the dwarfish race." +"druid" -> "Well, we have almost no druids in our town. If need arises we can call for them from Carlin." +"padreia" -> "Well, thats an trustworthy ally of dwarfs." +"good" -> "Well, good and evil will fight each other for all eternity." +"evil" -> * +"order" -> "Well, order is of great importance. Life has to follow rules, so do we." +"chaos" -> "Well, chaos is the ancient enemy. Dwarfs bring order to the world and give things shape. We are enemies of chaos." +"excalibug" -> "Well, well, well, the godblade. A myth? Perhaps. Even in my youth, it was only a legend." +"reward" -> "Well, isn't it reward enough to talk to the emperor of all dwarfs?" +"tbi" -> "Well, I don't think our southern allies have agents in our town." +"t.b.i." -> * +"edron" -> "Well, it is a colony of this human king." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/data/npc/kulag.npc b/data/npc/kulag.npc new file mode 100644 index 0000000..8a174e4 --- /dev/null +++ b/data/npc/kulag.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Kulag.npc: Datenbank für die Stadtwache am Westtor + +Name = "Kulag, the guard" +Outfit = (131,19-19-19-19) +Home = [32287,32264,07] +Radius = 2 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/data/npc/lea.npc b/data/npc/lea.npc new file mode 100644 index 0000000..c9a23d9 --- /dev/null +++ b/data/npc/lea.npc @@ -0,0 +1,120 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lea.npc: Datenbank für die Magierin Lea + +Name = "Lea" +Outfit = (138,59-95-94-113) +Home = [32348,31828,6] +Radius = 3 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> "Welcome back, %N!" +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Be patient %N, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care on your journeys." + +"bye" -> "Take care on your journeys.", Idle +"farewell" -> * +"job" -> "I am the archsorcerer of Carlin. I keep the secrets of our order." +"name" -> "My name is Lea." +"time" -> "Time is a force we sorcerers will master one day." +"wisdom" -> "You need great wisdom to cast spells of power." +"male" -> "Some tricks of sorcery are easy enough to be mastered even by males, but they'd better stick to cardtricks." +"sorcerer" -> "Any sorcerer dedicates his whole life to the study of the arcane arts." +"power" -> "We sorcerers wield arcane powers beyond comprehension of men." +"arcane" -> * +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Sorcerers, paladins, knights, and druids." +"spellbook" -> "A spellbook lists all your spells. There you can find the pronunciation of each spell. You can buy one at the magicians' shop." +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Take care on your journeys.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." +} diff --git a/data/npc/lector.npc b/data/npc/lector.npc new file mode 100644 index 0000000..07150a6 --- /dev/null +++ b/data/npc/lector.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lector.npc: Datenbank für den Metzger Lector + +Name = "Lector" +Outfit = (128,79-38-0-124) +Home = [32351,31795,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my humble shop, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Please come and buy again." + +"bye" -> "Please come and buy again.", Idle +"farewell" -> * +"job" -> "I am the butcher. I am selling delicious meat." +"butcher" -> * +"name" -> "My father named me Lector." +"father" -> "My father, Hannibal, was the royal cook. He died some years ago in an attack of the evil Ferumbras." +"time" -> "It is exactly %T." +"graveyard" -> "I heared, the mausoleum is haunted!" +"mausoleum" -> * +"dragon","steak" -> "Dragon steak was the favourite meal of Tark Trueblade." +"tark","trueblade" -> "Tark Trueblade was the greatest dragonslayer of all. I heared, he died in a battle with an ancient dragon lord." +"ghostlands" -> "A bloody place with a bloody history. I wonder if it realy drove people mad or if it just attracted those already disbalanced in their minds." + +"buy" -> "I can offer you ham or meat. Dragon steaks are out. " +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I can offer you ham or meat." + +"meat" -> Type=3577, Amount=1, Price=3, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=6, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=3*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=6*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/leedelle.npc b/data/npc/leedelle.npc new file mode 100644 index 0000000..70818b4 --- /dev/null +++ b/data/npc/leedelle.npc @@ -0,0 +1,176 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# leedelle.npc: Datenbank für die Händlerin Le'Delle (Newbie) + +Name = "Lee'Delle" +Outfit = (136,78-76-72-96) +Home = [32024,32196,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "I'm sorry %N, but I only serve premium account customers.", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Not now, not now, sorry %N. Please wait a moment.", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "I'm sorry %N, but I only serve premium account customers." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm delighted to welcome you as customer." +"sell" -> "I sell much. Have a look at the blackboards for my wares or just ask." +"job" -> "I am a merchant, so what can I do for you?" +"name" -> "My name is Lee'Delle. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I am already helping you by selling stuff." +"monster" -> "There are plenty of them. Buy here the equipment to kill them and sell their loot afterwards!" +"dungeon" -> "be carefull down there. Make sure you bought enough torches and a rope or you might get lost." +"sewer" -> "The sewers are full of rats. They are quite a challenge for inexperienced adventurers." +"king" -> "The king supports our little village very much!" +"dallheim" -> "He is a great warrior and our protector." +"bug" -> "There are several bugs in the wildernes." +"stuff" -> "I sell equipment of all kinds. Just ask me about the type of wares you are interested in." +"tibia" -> "The continent is even more exciting than this isle!" +"thais" -> "Thais is the capital of the thaian empire." + +"mission" -> "I really love flowers. Sadly my favourites, honey flowers are very rare on this isle. If you can find me one, I'll give you a little reward." +"quest" -> * +"reward" -> * + +"honey", "flower", Count(2984)>=1 -> "Oh, thank you so much! Please take this piece of armor as reward.",Amount=1, Delete(2984), Create(3362) +"honey", "flower" -> "Honey flowers are my favourites ." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "Sorry I fear the only picks left on this isle are in the posession of Al Dee." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","studded","armor" -> Type=3378, Amount=1, Price=25, "Do you want to sell a studded armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=25*%1, "Do you want to sell %A studded armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/leeland.npc b/data/npc/leeland.npc new file mode 100644 index 0000000..7b6802f --- /dev/null +++ b/data/npc/leeland.npc @@ -0,0 +1,37 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# leeland.npc: Datenbank für den Besitzer des allgemeinen Markts Leeland Slim + +Name = "Leeland" +Outfit = (130,19-36-96-76) +Home = [32883,32082,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hail$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute, %N. I am talking already, but will be avaliable for you very soon.", Queue +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N. Make sure to visit my shop." + +"bye" -> "Good bye, %N. Make sure to visit my shop.", Idle +"name" -> "My name is Slim, Leeland Slim." +"job" -> "I am the owner of the Useful Things Warehouse." +"warehouse" -> "I offer many things. Actually I am sure you will find something you have desired for a long time." +"time" -> "So it is a watch you need? Make sure to buy one downstairs." +"king" -> "Do I hear envy in your voice? Is it that you realy want? To be ... king? Well, one never knows ... perhaps you find something royal in my warehouse." +"tibianus" -> * +"army" -> "Your question suggests you are interested in military? I am sure you will find something interesting in my warehouse." +"ferumbras" -> "Ah, Ferumbras. I remember selling him torches for his first adventures as if it was yesterday." +"excalibug" -> "That's one of the few things even I can't aquire for you." +"thais" -> "Perhaps one day I will settle in thais again. I love the city's potential." +"tibia" -> "I have seen most of it. And I like it. " +"carlin" -> "I was there some time ago. I exchanged ideas with some important people there and even could sell them something that furthered their cause." +"news" -> "I would love to have the time to chat and exchange gossip, but sadly business always comes first, you know?" +"tax" -> "Taxes are a necessary evil, people say. I like that." +"privilege" -> "Privileges have to be paid for. The one way ... or the other." +"gambling" -> "I just love bets and gambling. It inspires people doing such interesting things." +} diff --git a/data/npc/legola.npc b/data/npc/legola.npc new file mode 100644 index 0000000..392a854 --- /dev/null +++ b/data/npc/legola.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# legola.npc: Datenbank für die Paladinin Legola + +Name = "Legola" +Outfit = (137,72-105-105-95) +Home = [32298,31784,7] +Radius = 2 + +Behaviour = { +ADDRESS,Paladin,"hello$",! -> "Hello, %N! Nice to see you." +ADDRESS,Paladin,"hi$",! -> * +ADDRESS,"hello$",! -> "Welcome to the Paladins, %N! What is your business here?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the local leader of the paladins' guild. I am trainer and teacher to our members." +"name" -> "My name is Legola. I am the head of the local paladins' guild." +"time" -> "It is %T." +"member" -> "Paladins profit from their chosen vocation. It has many advantages to be a paladin." +"profit" -> "The guild will help paladins to improve their skills. Besides we offer spells for our members." +"advantage" -> * +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Paladins, knights, sorcerers, and druids." +"paladin" -> "Paladins are great warriors and able magicians. Besides we are deadly missile fighters. Many people in Tibia want to join us." +"skill" -> * +"warrior" -> "Of course, we aren't as strong as knights, but no druid or sorcerer will ever defeat a paladin with a sword." +"magician" -> "Paladins learn to use most runes and can cast some usefull spells." +"missile" -> "Paladins are missile fighters, unequaled in Tibia!" +"woman" -> "All guild leaders in Carlin are chosen for their wisdom, and so all are women." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit the magicians' shop in the south of Carlin." +"ghostlands" -> "Many tried to break that curse, but the evil there is so deep and overwhelmig there seems to be no hope." + +"spell",Paladin -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to paladins." + +Topic=2,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=2,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Good bye.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=2 + +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=2 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=3 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=3 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=3 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You must be level %A to learn this spell." +Topic=3,"yes",CountMoney "Oh. You do not have enough money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Ok. Then not." +} diff --git a/data/npc/liane.npc b/data/npc/liane.npc new file mode 100644 index 0000000..a8a6f65 --- /dev/null +++ b/data/npc/liane.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# liane.npc: Datenbank für die Postfrau Liane + +Name = "Liane" +Outfit = (136,77-60-79-114) +Home = [32332,31784,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. May I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N. I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "It was a pleasure to help you." + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + +"kevin" -> "Kevin Postner was already leader of the guild as I joined. I can't imagine anyone better for that position." +"postner" -> * +"postmasters","guild" -> "Our guild relys heavily on the honor and trustworthyness of its members." +"join" -> "You might apply for a membership in our haedquarter." +"headquarter" -> "Its just south oh Kazordoon. Follow the road and you will run right into it." + + +"measurements",QuestValue(234)>0,QuestValue(239)<1 -> "I have more urgent problem to attend then that. Those hawks are hunting my carrier pigeons. Bring me 12 arrows and I'll see if I have the time for this nonsense. Do you have 12 arrows with you?",Type=3447, Amount=12,Topic=5 +"arrows",QuestValue(234)>0,QuestValue(239)<1 -> "Do you have 12 arrows with you?",Type=3447, Amount=12,Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Great! Now I'll teach them a lesson ... For those measurements ... ", Delete(Type),SetQuestValue(234,QuestValue(234)+1),SetQuestValue(239,1) +Topic=5,"yes" -> "Fool, you have no 12 arrows." +Topic=5 -> "Don't waste my time." + + +"job" -> "I am working here at the post office. If you have questions about the Royal Carlin Mail System or the depots ask me." +"office" -> "I rarely leave my office. You are welcome at any time." +"name" -> "My name is Liane." +"time" -> "Now it's %T." +#"mail" -> "Our mail system is unique! And so simple even males can use it. Do you want to know more about it?", Topic=1 +#"depot" -> "The depots are very easily to use. Just step in front of them and you will find your items in them. They are free for all citizens. Hail our Queen!" +"queen" -> "Our Queen's rule makes Carlin prosper." +"carlin" -> "Our wonderful town is protected by the wise Queen Eloise." +"thais" -> "A town ruled by men, a dangerous place. Anyway, we bring also letters and parcels there." +"benjamin" -> "He is the postman in Thais and somewhat stupid. But he never sents wrong letters or parcels." +"ghostlands" -> "We don't deliver letters or parcels there, sorry." +"wally" -> "Wally and I became pen-pals in the course of years." + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=1,"yes" -> "The Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else, I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/lightfoot.npc b/data/npc/lightfoot.npc new file mode 100644 index 0000000..b4a41d3 --- /dev/null +++ b/data/npc/lightfoot.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lightfoot.npc: Datenbank fuer den Rennhund Lightfoot + +Name = "Lightfoot" +Outfit = (32,0-0-0-0) +Home = [32914,32080,6] +Radius = 14 + +Behaviour = { +ADDRESS -> Idle +} diff --git a/data/npc/lily.npc b/data/npc/lily.npc new file mode 100644 index 0000000..80429c7 --- /dev/null +++ b/data/npc/lily.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lily.npc: Datenbank fuer die Druidin Lily + +Name = "Lily" +Outfit = (138,78-101-86-115) +Home = [32068,32225,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment, %N. I'll be with you in no time.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care." + +"bye" -> "Take care.", Idle +"farewell" -> * +"how","are","you" -> "Very well. Thank you." +"offer" -> "I only sell my antidote runes and I'll be happy to buy some blueberries from you." +"job" -> "I am a druid, bound to the spirit of nature. I'm selling antidote runes that help against poison. Oh, and I buy blueberries, of course." +"name" -> "My name is Lily." + +"hyacinth" -> "Hyacinth lives in the forest. He's never in town so I don't know him very well." +"time" -> "It is about %T." +"help" -> "I can sell you an antidote rune. It's against the poison of so many dangerous creatures." + +"monster" -> "Many monsters are poisonous. Don't let them bite you or you will need one of my antidote runes." +"creature" -> * +"poison" -> * +"life","fluid" -> "I'm sorry, but Hyacinth is the only one on Rookgaard who knows how to brew life fluids." + +"antidote" -> Type=3153, Data=1, Amount=1, Price=40, "Do you want to buy an antidote rune for %P gold?", Topic=1 +"rune" -> * +%1,1<%1,"antidote" -> Type=3153, Data=1, Amount=%1, Price=40*%1, "Do you want to buy %A antidote runes for %P gold?", Topic=1 +%1,1<%1,"rune" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you don't have enough gold." +Topic=1 -> "As you wish." + +"sell","blueberry" -> Type=3588, Amount=5, Price=1, "Do you want to sell 5 blueberries for %P gold?", Topic=2 +"sell","berry" -> * +"sell","blueberries" -> * +"sell","berries" -> * + +Topic=2,"yes",Count(Type)>=Amount -> "Fine! Here's your gold.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Oh, I'm sorry. I'm not buying less than 5 blueberries." +Topic=2 -> "As you wish." +} diff --git a/data/npc/livielle.npc b/data/npc/livielle.npc new file mode 100644 index 0000000..303d4cf --- /dev/null +++ b/data/npc/livielle.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Livielle.npc: Datenbank für die Nahrungsmittelhändlerin Livielle + +Name = "Livielle" +Outfit = (138,114-94-132-132) +Home = [32982,32036,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Ah, 'ello, %N! I can see you're longing for my delicious fruits, chéri." +ADDRESS,"hi$",male,! -> * +ADDRESS,"salut$",male,! -> * +ADDRESS,"hello$",female,! -> "Bienvenue, %N! My fruits will complete the icing on your cake." +ADDRESS,"hi$", female,! -> * +ADDRESS,"salut$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Don't push me, don't push me. One after another, %N." +BUSY,"hi$",! -> * +BUSY,"salut$",! -> * +BUSY,! -> NOP +VANISH,! -> "Aww, I don't even deserve a farewell?" + +"bye" -> "Bon appétit, and come back soon for your daily dose of vitamins!", Idle +"au","revoir" -> "Bon appétit, and come back soon for your daily dose of vitamins!", Idle +"job" -> "Alors, guess what my job might be, standing 'ere in the middle of all these juicy exotic fruits?" +"shop" -> * +"name",male -> "Moi? Livielle for you, chéri. " +"name",female -> "I'm Livielle Delacroix, madame." +"time" -> "Time is %T now." +"help" -> "Oh, for sure will my fruits 'elp you driving off all these nasty diseases and strengthen your immune system!" +"thanks" -> "You're welcome, enjoy." +"thank","you" -> * + +"buy" -> "What's your favorite flavour today? I offer all sorts of exotic fruits." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"fruits" -> "I offer you bananas, melons, pumpkins, white mushrooms, oranges, strawberries, and blueberries." + +"banana" -> Type=3587, Amount=1, Price=5, "Do you want to buy a banana for %P gold?", Topic=1 +"white","mushroom" -> Type=3723, Amount=1, Price=10, "Do you want to buy one of the white mushrooms for %P gold?", Topic=1 +"orange" -> Type=3586, Amount=1, Price=10, "Do you want to buy an orange for %P gold?", Topic=1 +"strawberr" -> Type=3591, Amount=1, Price=2, "Do you want to buy a strawberry for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=10, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 +"blueberr" -> Type=3588, Amount=1, Price=1, "Do you want to buy a blueberry for %P gold?", Topic=1 + +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=5*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=10*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=1 +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=10*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"strawberr" -> Type=3591, Amount=%1, Price=2*%1, "Do you want to buy %A strawberries for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=10*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 +%1,1<%1,"blueberr" -> Type=3588, Amount=%1, Price=1*%1, "Do you want to buy %A blueberries for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Merci, 'ere you go.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, but that's not enough money, please count again." +Topic=1 -> "You should really prefer my fruits over all this fat meat offered elsewhere. They keep you lithe and lissom." +} diff --git a/data/npc/lokur.npc b/data/npc/lokur.npc new file mode 100644 index 0000000..a289bb0 --- /dev/null +++ b/data/npc/lokur.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lokur.npc: Datenbank für den Postzwerg Lokur + +Name = "Lokur" +Outfit = (160,57-79-98-95) +Home = [32647,31904,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N. May I help you?" +ADDRESS,"hi$",! -> "Hiho %N. May I help you?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. One moment please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back if you need my services." + +"bye" -> "Come back if you need my services.", Idle +"farewell" -> * +"job" -> "I am the royal postdwarf and damned proud of it." +"office" -> "It's not big but I like the company." +"name" -> "My name is Lokur Stampsmasher, son of Earth of the Dragoneaters." +"time" -> "Too bad, I forgot my watch at home." +"mail" -> "The mail system was invented by dwarfs! Do you want me to tell you about it?", Topic=1 +#"depot" -> "Just walk towards them and you will find the items you stored in them during your last visit." +"king" -> "Our king has a treasure room and does not need a depot." +"carlin" -> "Imagine, they have a postoffice their too, jawoll." +"thais" -> * + +"kevin" -> "Ah, this human is persistant as a dwarf. A worthy leader indeed, jawoll." +"postner" -> * +"postmasters","guild" -> "The guild keeps things running. Organized and reliable. I appreciate that, jawoll." +"join" -> "Our members are handpicked by Kevin postner in our headquarter." +"headquarter" -> "Its south of kazordoon. Just follow that road, can't miss it." + + +"measurements",QuestValue(235)>0 -> "Ask Kroox about that stuff." +"measurements",QuestValue(234)>0 -> "Come on, I have no clue what they are. Better ask my armorer Kroox for such nonsense. Go and ask him for good ol' Lokurs measurements, he'll know.",SetQuestValue(235,1) + +@"gen-post.ndb" + +#"letter" -> Amount=1, Price=5, "So you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "So you want to buy a parcel for %P gold?", Topic=3 + +Topic=1,"yes" -> "The mail system enables you to send and receive letters and parcels. You can buy them here if you want." +Topic=1 -> "Is there anything else, I can do for you?" + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you have not enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you have not enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/lorbas.npc b/data/npc/lorbas.npc new file mode 100644 index 0000000..82616bc --- /dev/null +++ b/data/npc/lorbas.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lorbas.npc: Datenbank für den falschen mönch lorbas + +Name = "Lorbas" +Outfit = (57,0-0-0-0) +Home = [32695,32310,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Accept my sincere good wishes." + +"bye" -> "Accept my sincere good wishes.", Idle +"farewell" -> * +"job" -> "I am just a humble monk and responsible to maintain this little outpost that is leftover from our grand order." +"monk" -> "We monks of the humble path feel that we are not worthy to spread the word of the gods. We live in humility and poverty to serve the gods. Most of us have vowed an oath of silence and I humbly took the burden to become the spokesperson." +"order" -> "Our order was once the greatest and richest in the whole known world. Kings, traders and knights of various orders were our supporters and the gods smiled upon us ... or at least that's what we thought until the day of doom." +"time" -> "I own no watch and only a small number of other worldly possessions." +"day","doom" -> "On the day of doom, our dream of building the greatest and most opulent cathedral was shattered." +"shattered" -> "The cathedral was already high and impressive, the order had started to move in although there was still much left to be done. Then the great earthquake came." +"earthquake" -> "Some say it was just the unstable ground or volcanic activity, some even claim it was the work of demons, but we know it was the will of the gods to punish our vanity." +"vanity" -> "In our vanity we thought that we could impress the gods with our money and show piety by building them a monument. ...", + "We were wrong and the gods punished us by sending the worst earthquake that mankind has seen. ...", + "Its ground motions could still be felt in Thais and as the dust settled, little had remained of that what we had built. ...", + "Most members of our order were dead, others turned mad or lost faith. We are all that is left from our glorious order." +"cathedral" ->"What was once planned as the most impressive cathedral of all times, lies now in ruins. ...", + "All what the earthquake has left over is a heap of rubble. The ruins are cursed and everybody who dares to go there will draw the ire of heaven on himself. ...", + "All those that travel there are infested with bad luck. But only few have returned from this treacherous ground. Noxious fumes are killing intruders almost unnoticed. ...", + "Crumbling structures might kill you instantly. ...", + "Those who survive the dangers of nature will face the soul-eating ghosts of those who have died in the catastrophe. ...", + "It's not worth to go there, there are no richnesses or treasures left in the ruins, the gold of our order melted away in funding the cathedral's construction. ...", + "I urge you to stay away from the cursed ground and the ruins. For the safety of your body and your soul keep away from there." +"king" -> "The king is a worldly ruler, and we don't burden ourselves with worldly concerns anymore." +"venore" -> "The gracious tradesmen from Venore send us provisions from time to time." +"thais" -> "Thais is far and we have little contact with the kingdom's capital." +"carlin" -> "We have no relations with that town." +"edron" -> "I hope the knightly order there fares better than our own." +"gods" -> "I am not worthy to speak about the gods." + +"tibia" -> "The world is in the hand of the gods." + +"kazordoon" -> "The dwarves are far from being humble. At least this ancient folk knows that there is nothing to gain in the cathedral's ruins and their treasure hunters stay away from there." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "They will have to learn on their own." +"elves" -> * +"elfs" -> * +"darama" -> "Another continent that has to be seen as a present of the gods to us." +"darashia" -> "I will not judge those people." +"ankrahmun" -> "I am not the right person to discuss this subject." +"ferumbras" -> "He will discover where his path will lead him to. But no matter how ruthless he is, even he stays away from the ruins of the cathedral." +"excalibug" -> "It is rumoured to be hidden somewhere beneath Edron." +"assassin" -> "I know nothing about that topic. If you would excuse me, I have things to attend.",idle +"dark","monk" -> * +} diff --git a/data/npc/lorek.npc b/data/npc/lorek.npc new file mode 100644 index 0000000..2aec46b --- /dev/null +++ b/data/npc/lorek.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Lorek.npc: Datenbank für den Fährman Lorek + +Name = "Lorek" +Outfit = (132,19-10-38-95) +Home = [32679,32775,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Just wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am a ferryman. If you want me to transport you to the other end of the city, feel free to ask me for a passage." +"name" -> "I am Lorek." +"time" -> "I have no idea." +"king" -> "I wonder if he will inspect our colony some day." +"venore" -> "It seems the traders are incredibly rich." +"thais" -> "I left Thais for the opportunities that might be found here." +"carlin" -> "I am not sure if we are at war with them. I think they defy the rule of our king." +"edron" -> "Edron has to be very pretty and all people there are rich and such." +"jungle" -> "I can only hope that the guards protect us all from those dangerous beasts out there." + +"tibia" -> "The world is so big that it often scares me." + +"kazordoon" -> "I overheard the dwarves talking about it. I have no idea what it is though." +"dwarves" -> "There are some dwarves living here." +"dwarfs" -> * +"ab'dendriel" -> "What?" +"elves" -> "I only heard of them, but I never saw one. It's said that they have funny ears." +"elfs" -> * +"darama" -> "If more people move to Darama, I might get a better job and earn a fortune." +"darashia" -> "Another human settlement on this continent. It's somewhere in the desert though." +"ankrahmun" -> "They say it's a city full of undead and half-dead people. What a horrible thought!" +"ferumbras" -> "I heard he is some scary magician or so." +"excalibug" -> "What's that?" +"apes" -> "If only the guards could stop their constant attacks." +"lizard" -> "I have only heard about them. I hope they won't come here." +"dworcs" -> "Those little greenskins are more dangerous than a cobra." + + +"trip" -> "I can bring you either to the centre of Port Hope or to the west end of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"cent" -> Price=7, "Do you seek a passage to the centre of Port Hope for %P gold?", Topic=1 +"west" -> Price=7, "Do you seek a passage to the west end of Port Hope for %P gold?", Topic=2 + + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + +#"trip" -> Price=7, "Would you like to travel to the other end of Port Hope or to the centre of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * + +#Topic=1,"end",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"centre",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"end",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +#Topic=1,"end",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"centre",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32628,32771,7), EffectOpp(11) +#Topic=1,"centre",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "HELLO? Anyone in there? I was asking you WHERE you want to travel!" + +} diff --git a/data/npc/loria.npc b/data/npc/loria.npc new file mode 100644 index 0000000..ce0a6c3 --- /dev/null +++ b/data/npc/loria.npc @@ -0,0 +1,109 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# loria.npc: Datenbank für die Zauberkundige Loria + +Name = "Loria" +Outfit = (138,96-118-82-95) +Home = [32385,32131,7] +Radius = 10 + +Behaviour = { +ADDRESS,"hello","loria",! -> "Welcome %N, my friend." +ADDRESS,"hi","loria",! -> * +ADDRESS,"hello",! -> "Welcome %N." +ADDRESS,"hi",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hope to see you again." + +"bye" -> "May the magic be with you, %N.", Idle +"farewell" -> * +"job" -> "I am studying the power of magic all the time." +"lake" -> "I hope you like it. It is named like my master, Alatar, the Sage" +"name" -> "I am Loria, a former apprentice of Alatar, the Sage." +"alatar" -> "Well, he was my great master. He taught me all these fantastic things about magic. I really miss him." +"time" -> "Time means nothing to me." +"buy" -> "I don't care for money, I care for magic." +"power" -> "Although your attack spells get stronger with your usage of magic, real power is gained by finding strategies to properly use your magic abilities." +"mana" -> "Mana is the source of all magic. If you use spells, it will drain mana from your energy pool. This mana regenerates slowly, if you eat, or if you drink those mana fluids you can buy at Xodet's." +"gorn" -> "He runs an equipment shop close to the north gate of the city." +"xodet" -> "He runs a magic shop in the main road." +"praise","alatar" -> "I praise my master Alatar." + +"kill" -> "Killing and destruction are just foolish steaps to entrophy." + +"quest" -> "I heard from a mystic bone of the lich lord below the House of Necromant. Bring it to me, and you will receive a reward." +"crystal" -> "The mystic crystal should be able to resurrect fresh corpses." +"necromant" -> "He lived in a lonely house in the south eastern part of Tibia beyond the mountains." +"reward" -> "I'll teach you a very seldom spell.", Topic=4 +Topic=4,"spell" -> "I'll teach you 'exevo gran mas vis', but bring me this bone first!" + +"magic",Knight -> "I could tell you much about all sorcerer spells, but you won't understand it. Anyway, feel free to ask me." +"magic" -> "Oh, I can tell you a lot about all sorcerer spells. Feel free to ask me." +"spell" -> "Oh, I can tell you a lot about all sorcerer spells. Feel free to ask me." +"rune" -> "All spells starting with the syllable 'ad' must be burned into a rune. For this buy a rune from Xodet and put it in one of your hands. Now cast the formula of the spell." +"Muriel" -> "He runs his magic shop in the southwest of the city. He sells runes and spells and helps you, if you want to become a sorcerer." +"find","person" -> "If you search someone, this spell will give you an idea of the direction you must head. You will be able to see, whether he is below or above you." +"light" -> "A ray of light will emerge from your flat hand to illuminate your environment." +"light","healing" -> "The paths to the next temple are long. Even in Tibia. So learn this spell, and be able to heal yourself during your travels. This spell will only cure small wounds, but it is pretty helpful." +"light","magic","missile" -> "You can activate this spell by pointing your index finger in the direction of your enemy, conjure the power of your rune and shoot the magic missiles in your enemy's body." +"antidote" -> "This spell sucks the venom out of your veins, that some enemy might have injected." +"intense","healing" -> "This spell will cure more wounds or greater ones at once. This is of course more 'mana intensive', but everybody will sooner or later get in a situation where mana is nothing - compared to life." +"poison","field" -> "This spell will create a single field of poisonous gas. Cast it on a creature you were not able to arrange a peace treaty with. If it has no antidote, watch what could happen if you forget yours." +"great","light" -> "This spell will illuminate your whole screen and last longer than 'light'. Use it in deep dungeons, 'cause behind every corner there could be your last enemy ... and you might just walk into him." +"fire","field" -> "This spell acts similar to the 'poison field' spell, except that you create fire instead of poisonous gas. Don't enter it yourself or you will realize why it is said that 'fire eats everything'." +"heavy","magic","missile" -> "Remember the spell where you only got to wave your hand? Well, wave it twice and shoot a heavy magic missile at your enemy. This spell will create a rune with five charges." +"magic","shield" -> "Well, mages are more bookworms than sportsmen, and as such often neglect their physical fitness. In ancient tomes lies the power to use mana as an equivalent to life. So use this spell to survive." +"fireball" -> "A perfect symbiosis of fire and wind. More is not to be said about this spell. Use this fireball as a warning or as your defence, but don't burn your fingers." +"energy","field" -> "This one will create a field of energy. Everyone stepping in will be struck from lightning. This field will not last as long as poison or fire fields, but it is more deadly." +"destroy","field" -> "Trapped again between fire, poison and energy fields? This spell will give you the ability to destruct the fields, so you can pass safely." +"fire","wave" -> "Turn to you opponent and release the forces of nature with a whisper of your voice. A triangle of fire will burn all persons in your view, so take care, in which direction you look!" +"ultimate","healing" -> "This spell is able to cure almost every injury at a higher cost than the other healing spells." +"great","fireball" -> "Imagine scaling the normal fireball by two and raising the fire temperature." +"firebomb" -> "With a snip of your finger you can cover the floor with a burning carpet that keeps on burning for a while." +"fire","bomb" -> "With a snip of your finger you can cover the floor with a burning carpet that keeps on burning for a while." +"energybeam" -> "A ray of energy will strike everyone in your current direction" +"creature","illusion" -> "A good one to scare childs. You can change your appearance to any monster. You can be as handsome as a ghoul!" +"poison","wall" -> "With this one you can create a huge wall of poisonous gas. Many monsters will be too scared to pass the wall and if they do, they will choke from nausea." +"explosion" -> "A strong blast of fire wounds the opponent you point at, and the adjacent squares." +"fire","wall" -> "As the poison wall, this spell creates an even larger wall of fire, burning everyone who passes." +"great","energy","beam" -> "A lightning bolt strikes the point you look at." +"invisible" -> "This spell drains the colors out of you body, making yourself invisible for an hour or two." +"summon","creature" -> "This one gives you the ability to summon monsters that aid you in your battles." +"energy","wall" -> "Attracts lightning bolts from the sky, to form a giant wall, seriously damaging everyone who passes." +"energy","wave" -> "Shoots a triangular bundle of lightning bolts in the direction you look." +"sudden","death" -> "The best spell for deciding a battle within seconds. The spell tries to interrupt the opponents heart beat, leading to his instant death in most cases." + +"formula" -> "Which is the spell, you need the formula to?", Topic=1 +Topic=1,"find","person" -> "Say the words: exiva 'name'" +Topic=1,"light" -> "Say the words: utevo lux" +Topic=1,"light","healing" -> "Say the word: exura" +Topic=1,"light","magic","missile" -> "Say the word: adori" +Topic=1,"antidote" -> "Say the words: exana pox" +Topic=1,"intense","healing" -> "Say the words: exura gran" +Topic=1,"poison","field" -> "Say the words: adevo grav pox" +Topic=1,"great","light" -> "Say the words: utevo gran lux" +Topic=1,"fire","field" -> "Say the words: adevo grav flam" +Topic=1,"heavy","magic","missile" -> "Say the words: adori gran" +Topic=1,"magic","shield" -> "Say the words: utamo vita" +Topic=1,"fireball" -> "Say the words: adori flam" +Topic=1,"energy","field" -> "Say the words: adevo grav vis" +Topic=1,"destroy","field" -> "Say the words: adito grav" +Topic=1,"fire","wave" -> "Say the words: exevo flam hur" +Topic=1,"ultimate","healing" -> "Say the words: exura vita" +Topic=1,"great","fireball" -> "Say the words: adori gran flam" +Topic=1,"fire","bomb" -> "Say the words: adevo mas flam" +Topic=1,"firebomb" -> "Say the words: adevo mas flam" +Topic=1,"energy","beam" -> "Say the words: exevo vis lux" +Topic=1,"creature","illusion" -> "Say the words: utevo res ina 'creature'" +Topic=1,"poison","wall" -> "Say the words: adevo mas grav pox" +Topic=1,"explosion" -> "Say the words: adevo mas hur" +Topic=1,"fire","wall" -> "Say the words: adevo mas grav flam" +Topic=1,"great","energy","beam" -> "Say the words: exevo gran vis lux" +Topic=1,"invisible" -> "Say the words: utana vid" +Topic=1,"summon","creature" -> "Say the words: utevo res 'creature'" +Topic=1,"energy","wall" -> "Say the words: adevo mas grav vis" +Topic=1,"energy","wave" -> "Say the words: exevo mort hur" +Topic=1,"sudden","death" -> "Say the words: adori vita vis" +} diff --git a/data/npc/loui.npc b/data/npc/loui.npc new file mode 100644 index 0000000..99be514 --- /dev/null +++ b/data/npc/loui.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Loui.npc: Datenbank für Loui den ängstlichen Mönch + +Name = "Loui" +Outfit = (57,0-0-0-0) +Home = [32014,32190,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "BEWARE! Beware of that hole!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, you may listen to the frightening story I am telling!", Queue +BUSY,"hi$",! -> * + +BUSY,! -> NOP +VANISH,! -> "STAY AWAY FROM THAT HOLE!" + +"bye" -> "May the gods protect you! And stay away from that hole!", Idle +"farewell" -> * +"job" -> "I am a monk, collecting healing herbs." +"name" -> "My name is Loui." +"monk" -> "I am a humble servant of the gods." +"tibia" -> "Everything around us, that is Tibia." +"rookgaard" -> "This is the place where everything starts." +"god" -> "They created Tibia and all lifeforms. Talk to other monks and priests to learn more about them." +"life" -> "The gods blessed Tibia with abundant forms of life." +"herb" -> "I was looking for some herbs as I foolishly entered this unholy hole." +"obi" -> "He owns a shop in the town." +"al","dee" -> "He owns a shop in the town." +"seymour" -> "Seymour is the headmaster of the local academy." +"academy" -> "Most adventurers take their first steps there." +"willie" -> "The gods may protect me from his foul language." +"monster" -> "There must be an army of them, just down this hole." +"rabbit" -> "So it must have been some magic wielding beasts using creature illusion. Good thing you escaped." + +"quest" -> "I have no quests but to stay away from that hole and I'd recomend you to do the same." +"task" -> * + +"gold" -> "I am pennyless and poor as it is fit for a humble monk like me." +"money" -> * +"rat" -> "The good thing is, those horrible rats stay in the town mostly. The bad thing is, they do so because outside the bigger Monsters devour them." +"hole" -> "While looking for herbs I found that hole. I went down though I had no torch. And then I heard THEM! There must be dozens!" +"story" -> * +"them" -> "They were so many, EVERYWHERE! I could barely escape alive. I have no clue what THEY were but one more second down there and I'd be dead!" + +"heal" -> "Sorry I am out of mana and ingredients, please visit Cipfried in the town." +"time" -> "Now, it is %T, my child." + + +} diff --git a/data/npc/lubo.npc b/data/npc/lubo.npc new file mode 100644 index 0000000..c24c904 --- /dev/null +++ b/data/npc/lubo.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lubo.npc: Datenbank fuer Lubo, den Haendler im Abenteurer-Laden + +Name = "Lubo" +Outfit = (129,38-39-96-114) +Home = [32488,32119,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my adventurer shop, %N! What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment for adventurers. Do you need anything?" +"name" -> "I am Lubo, the owner of this shop." +"time" -> "It is exactly %T." +"mountain" -> "It is said that once there lived a great magician on the top of this mountain." +"magician" -> "I don't remember his name, but it's said that his banner was the black eye." +"food" -> "I sell the best apples in Tibia." +"map" -> "Oh! I'm sorry, I sold the last one just five minutes ago." +"magic" -> "There's a lot of magic flowing in the mountain to the north." +"weapon" -> "If you want to buy weapons, you'll have to go to a town or city." +"dog" -> "This is Ruffy my dog, please don't do him any harm." +"pet" -> "There are some strange stories about a magicians pet names. Ask Hoggle about it." +"finger" -> "Oh, you sure mean this old story about the mage Dago, who lost two fingers when he conjured a dragon." + +"inn" -> "Frodo runs a nice inn in the near town Thais." +"crunor","cottage" -> "Ah yes, I remember my grandfather talking about that name. This house used to be an inn a long time ago. My family bought it from some of these flower guys." +"flower","guy" -> "Oh, I mean druids of course. They sold the cottage to my family after some of them died in an accident or something like that." +"accident" -> "As far as I can remember the story, a pet escaped its stable behind the inn. It got somehow involved with powerfull magic at a ritual and was transformed in some way." +"stable",QuestValue(211)=3 -> "My grandpa told me, in the old days there were some behind this cottage. Nothing big though, just small ones, for chicken or rabbits.",SetQuestValue(211,4) +"stable",QuestValue(211)<3 -> "Sorry speak louder I can't hear you." + +"equipment" -> "I sell torches, fishing rods, sixpacks of worms, ropes, water hoses, backpacks, apples, and maps." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=3, "Do you want to buy a torch for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=60, "Do you want to buy a rope for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you want to buy a water hose for %P gold?", Topic=1 +"backpack" -> Type=2854, Amount=1, Price=25, "Do you want to buy a backpack for %P gold?", Topic=1 +"fishing","rod" -> Type=3483, Amount=1, Price=175, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a crowbar for %P gold? I know its rather expensive, but I must protect people from thieves.", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 +%1,"torch" -> Type=2920, Amount=%1, Price=3*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/lugri.npc b/data/npc/lugri.npc new file mode 100644 index 0000000..ca77e7f --- /dev/null +++ b/data/npc/lugri.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lugri.npc: Datenbank fuer den Zathrothpriester Lugri + +Name = "Lugri" +Outfit = (9,0-0-0-0) +Home = [32389,32118,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "What do you want, %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "SILENCE!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May darkness be with you!" + +"bye" -> "Leave now, %N. The gods of darkness will watch your soul!", Idle +"farewell" -> * +"job" -> "I am a priest of Zathroth, the bringer of dark secrets." +"name" -> "My name is Lugri." +"news" -> "You will soon see the 'news' with your own eyes. " +"tibia" -> "The world of Tibia is to be taken by the strongest." +"how","are","you"-> "I feel the power of evil rising and enjoy that." +"sell" -> "I am in the death business. You wouldn't like what I have to offer." +"god$" -> "The gods of darkness give us the chance to reach our whole potentials, the gods of good want to capture us in eternal stasis!", Topic=2 +"gods$" -> * +"life" -> "Life is war. It's about survival of the fittest." +"citizen" -> "The people of Tibia are sheep, so be smart and strong enough to become their wolf." +"people" -> * +"king" -> "This puny king is no threat for our master's plans." +"monster" -> "They are a challenge to sift the chaff from the wheat." +"quest" -> "Aren't we all on a quest for survival and supremacy?" +"mission" -> * +"survival" -> * +"supermacy" -> * +"gold" -> Price=30, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "Life is an eternal fight!" +"slay" -> "The weak have to be slain by the strong!" +"heal" -> "Your wounds are your problem, not mine." +"help" -> "If you cant help yourself you are not worth of my assistance." +"ferumbras" -> "He is one of Zathroth's strongest followers and wields special powers, given to him by the dark one." +"time" -> "Who cares?" +"excalibug" -> "It's existence is just a lie to inspire hope and bravery in the hearts of the followers of good." + +Topic=2,"good" -> "The so called gods of good are Fardos, Uman, the elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +Topic=2,"light" -> "The so called gods of good are Fardos, Uman, the elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator. He is a helpless watcher whose 'creation' is far more then he bargained for." +"uman" -> "Uman is a jealous keeper of magic. He gives only little knowledge to the mortals." +"suon" -> "Suon is one of the suns of our world. He gives his light mindlessly to the weak and the strong alike." +"crunor" -> "Crunor is a plantgod ... and plants exist to be stomped over." +"nornur" -> "Nornur fancies himself as god of fate without even understanding the ways of fate at all." +"bastesh" -> "Bastesh is so afraid that she hides in the depth of the seas." +"kirok" -> "Kirok, the mad one, is the patron of scientists and jesters, more a nuisance than a god." +"toth" -> "Toth is just the undertaker for the other 'gods of good'." +"banor" -> "Banor isn't a god at all, but one of their tools. It is stupidity to worship a tool, isn't it?" +"tibiasula" -> "Zathroth took her life, recoginzig it was necessary for the process of creation." +Topic=2,"tibia" -> "Tibia is just the mindless elemental power of earth." +"sula" -> "Sula is just the mindless elemental power of water." +"air" -> "Air is is just a mindless elemental force." +"fire" -> "Fire is is just a mindless elemental force." + +Topic=2,"evil" -> "The glorious gods of darkness are Zathroth, Fafnar, Brog, Urgith, and the Archdemons." +Topic=2,"darkness"-> "The glorious gods of darkness are Zathroth, Fafnar, Brog, Urgith, and the Archdemons." +"zathroth" -> "Zathroth represents the true and unbound power of magic. He is the keeper of great secrets." +"fafnar" -> "Fafnar is the power of the sun. She burns the weak to ashes." +"brog" -> "Brog, the raging one, the great destroyer, the berserk of darkness ... call him how you like, but fear his awesome power." +"urgith" -> "Urgith is the master of the undead. The bonemaster also takes care of the damned souls." +"archdemons" -> "The demons are powerful followers of Zathroth. Their leaders are known as the ruthless seven." +"ruthless","seven"-> "Infernatil, Pumin, Verminor, Tafariel, Apocalypse, Bazir and Ashfalor." +"tafariel" -> "She is the mistress of the damned! Rewarding or torturing, it is the same for her victims!" +"apocalypse" -> "It is said even speaking its TRUE name will bring total destruction to you!" +"pumin" -> "He is the lord of despair." +"infernatil" -> "The incendiary of hell." +"bazir" -> "He is the great deciver, the lord of lies." +"Verminor" -> "Ah, the plaguelord." +"ashfalor" -> "The right hand of Urgith. The general of the undead hordes." +"pits","inferno" -> "After the ruthless seven conquered it, it's again a holy place for the followers of the dark path." +"nightmare","pits"-> "That name is a disgrace. The pityful nightmare knights couldn't defend them and even lost the treasure of their order there." +"goshnar" -> "The necromant king was only defeated by the nightmare knights due to a bad twist of fate." +"necromant","nectar"-> "That's none of your business!" + +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes" -> "Don't be ashamed but you lack the gold." +Topic=1 -> "As you wish." + +"death","to","noodles" -> Type=3061, Amount=1, "So, I guess you bring me a magic crystal?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "Fine. Now you get what you deserve, you fool! DIE IN AGONY!", Burning(25,25), EffectOpp(6), EffectMe(14), Delete(Type), Idle +} diff --git a/data/npc/luna.npc b/data/npc/luna.npc new file mode 100644 index 0000000..b50008d --- /dev/null +++ b/data/npc/luna.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# luna.npc: Datenbank für die Kräuterhändlerin Luna + +Name = "Luna" +Outfit = (137,0-118-100-115) +Home = [33254,31840,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I'm too busy now." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye, traveller." + +"bye" -> "Goodbye, traveller.", Idle +"name" -> "I'm called Luna." +"job" -> "I sell various herbs, mushrooms, and flowers." +"time" -> "Sorry, I don't know." +"king" -> "I don't know much about the king, sorry." +"tibianus" -> * +"army" -> "I sometimes heal soldiers with my herbal mixtures." +"heal" -> * +"ferumbras" -> "Mentioning his name makes me shiver." +"excalibug" -> "I am not an expert for weapons." +"thais" -> "I prefer the wilderness to cities." +"tibia" -> * +"carlin" -> * +"edron" -> * +"news" -> "I fear I know nothing new that is of any importance." +"rumors" -> * + +"offer" -> "I'm selling various herbs, mushrooms, and flowers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"herbs" -> "I have stone herbs, star herbs, and ferns. What do you want?" +"mushroom" -> "I have white, red, and brown mushrooms. Which one do you want?" +"flowers" -> "I have red roses and tulips. What do you want?" + +"white","mushroom" -> Type=3723, Amount=1, Price=6, "Do you want to buy one of the white mushrooms for %P gold?", Topic=1 +"red","mushroom" -> Type=3724, Amount=1, Price=12, "Do you want to buy one of the red mushrooms for %P gold?", Topic=1 +"brown","mushroom" -> Type=3725, Amount=1, Price=10, "Do you want to buy one of the brown mushrooms for %P gold?", Topic=1 + +%1,1<%1,"white","mushroom" -> Type=3723, Amount=%1, Price=6*%1, "Do you want to buy %A of the white mushrooms for %P gold?", Topic=1 +%1,1<%1,"red","mushroom" -> Type=3724, Amount=%1, Price=12*%1, "Do you want to buy %A of the red mushrooms for %P gold?", Topic=1 +%1,1<%1,"brown","mushroom" -> Type=3725, Amount=%1, Price=10*%1, "Do you want to buy %A of the brown mushrooms for %P gold?", Topic=1 + + +"rose" -> Type=3658, Amount=1, Price=11, "Do you want to buy a red rose for %P gold?", Topic=1 +"tulip" -> Type=3668, Amount=1, Price=9, "Do you want to buy a tulip for %P gold?", Topic=1 +"stone","herb" -> Type=3735, Amount=1, Price=28, "Do you want to buy a stone herb for %P gold?", Topic=1 +"star","herb" -> Type=3736, Amount=1, Price=21, "Do you want to buy a star herb for %P gold?", Topic=1 +"fern" -> Type=3737, Amount=1, Price=24, "Do you want to buy a fern for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/lungelen.npc b/data/npc/lungelen.npc new file mode 100644 index 0000000..784c7c7 --- /dev/null +++ b/data/npc/lungelen.npc @@ -0,0 +1,17 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lungelen.npc: Datenbank für die Erzmagierin Lungelen + +Name = "Lungelen" +Outfit = (138,77-19-95-115) +Home = [32303,32267,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Please don't disturb me, I am very busy in my recent researches. Have a nice day!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> NOP +BUSY,! -> NOP +VANISH,! -> NOP +} diff --git a/data/npc/lynda.npc b/data/npc/lynda.npc new file mode 100644 index 0000000..aaa43b7 --- /dev/null +++ b/data/npc/lynda.npc @@ -0,0 +1,158 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# lynda.npc: Datenbank für die Priesterin Lynda + +Name = "Lynda" +Outfit = (138,79-83-86-114) +Home = [32333,32200,7] +Radius = 4 + +Behaviour = { +ADDRESS,male,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours life could bring upon you?", Topic=9 +ADDRESS,female,"my","heart","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours life could bring upon you?", Topic=9 +ADDRESS,"hello$","lynda",! -> "Welcome in the name of the gods, pilgrim %N!" +ADDRESS,"hi$","lynda",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N! Please be patient, my child.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods be with you!" + +"bye" -> "Good bye, %N. May the gods guard you, my child!", Idle +"farewell" -> * +"job" -> "I am a priest of the great pantheon." +"news" -> "Sorry, I had no enlightening visions lately." +"name" -> "My name is Lynda. And the spirits tell me that you are %N." +"tibia" -> "The world of Tibia is the creation of the gods." +"how","are","you"-> "Thank you, I'm fine, the gods are with me." +"sell" -> "The grace of the gods must be earned, not bought!" +"sin$" -> "Do you whish to confess your sins?", Topic=3 +"sins$" -> "Do you whish to confess your sins?", Topic=3 +"god$" -> "The gods of good guard us and guide us, the gods of evil want to destroy us and steal our souls!", Topic=2 +"gods$" -> * +"life" -> "Life is a gift of the gods, honor life and don't destroy it." +"citizen" -> "The things I know about our citizens are confidential." +"lugri" -> "He is a follower of evil. May the gods punish him." +"king" -> "King Tibianus is our benevolent sovereign." +"monster" -> "They are creatures of the gods of evil!" +"quest" -> "It is my mission to spread knowledge about the gods." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "It is MY mission to teach, it is YOUR mission to fight!" +"slay" -> * + +"help",HP<40,! -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0,! -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0,! -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) + +"heal$",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +"ferumbras" -> "He is a favourite of the gods of evil and one of the champions of evil." +"time" -> "Now, it is %T." +"excalibug" -> "This fabled weapon was lost in ancient times. If someone found it, this person would be nearly invincible." + +Topic=2,"good" -> "The gods we call the good ones are Fardos, Uman, the Elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator, the great obsever. He is our caretaker." +"uman" -> "Uman is the positive aspect of magic. He brings us the secrets of the arcane arts." +"suon" -> "Suon is the lifebringing sun. He observes the creation with love." +"crunor" -> "Crunor, the great tree, is the father of all plantlife. He is a prominent god for many druids." +"nornur" -> "Nornur is the mysterious god of fate. Who knows if he is its creator or just a chronist?" +"bastesh" -> "Bastesh, the deep one, is the goddess of the sea and its creatures." +"kirok" -> "Kirok, the mad one, is the god of scientists and jesters." +"toth" -> "Toth, lord of death, is the keeper of the souls, the guardian of the afterlife." +"banor" -> "Banor, the heavenly warrior, is the patron of all fighters against evil. He is the gift of the gods to inspire humanity." +"tibiasula" -> "Tibiasula lost her life, but out of her essence the world was created." + +Topic=2,"tibia" -> "Tibia is the essence of the elemental power of earth." +"sula" -> "Sula is the essence of the elemental power of water." +"air" -> "Air is one of the primal elemental forces, sometimes worshipped by tribal shamans." +"fire" -> "Fire is one of the primal elemental forces, sometimes worshipped by tribal shamans." + +Topic=2, "evil" -> "The gods we call the evil ones are Zathroth, Fafnar, Brog, Urgith, and the Archdemons!" +"zathroth" -> "Zathroth is the destructive aspect of magic. He is the deceiver and the thief of souls." +"fafnar" -> "Fafnar is the scorching sun. She observes the creation with hate and jealousy." +"brog" -> "Brog, the raging one, is the great destroyer. The berserk of darkness." +"urgith" -> "The bonemaster Urgith is the lord of the undead and keeper of the damned souls." +"archdemons" -> "The demons are followers of Zathroth. The cruelest are known as the ruthless seven." +"ruthless", "seven" -> "I dont want to talk about that subject!" + +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes",CountMoney "Dont be ashamed, but you lack the gold." + +Topic=3,"yes" -> "So tell me what shadows your soul, my child.", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and pray for your soul." + +"marriage" -> "You want me to initiate a marriage ceremony?", Topic=5 +"ceremony" -> * +Topic=5,"yes" -> "In the Name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time. Marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence please! I hereby invoke the attention of the eternal powers looking over our souls and lives. May the gods bless us!", EffectMe(13), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours life could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours life could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,male,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(14), Idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further life as a married couple. Go now and celebrate your marriage!", EffectOpp(14), EffectMe(13), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", Idle +} diff --git a/data/npc/maealil.npc b/data/npc/maealil.npc new file mode 100644 index 0000000..daf1d49 --- /dev/null +++ b/data/npc/maealil.npc @@ -0,0 +1,134 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maealil.npc: Datenbank für die Mystikerin Maealil (Elfenstadt) + +Name = "Maealil" +Outfit = (63,0-0-0-0) +Home = [32732,31631,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, please wait a moment %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi, traveller." + +"bye" -> "Asha Thrazi, traveller.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I am a mystic." +"name" -> "I am known as Maealil." +"time" -> "I don't own one of those little machines." +"mystic" -> "I am a philosopher and healer." + +"elves" -> "We are an ancient race, abandoned by the gods and doomed to find our way alone." +"dwarfs" -> "They cultivate earth but don't understand it." +"humans" -> "They are somewhat orcish in their nature." +"troll" -> "I don't think it's a good idea to keep servants." + +"cenath" -> "My parents were Deraisim but joined the Cenath caste before my birth." +"kuridai" -> "I hope they don't do something foolish one day." +"deraisim" -> "Unfortunately they are to busy to care for the finer things in life." +"abdaisim" -> "They should join our town for their and our own safety." +"teshial" -> "I would love to learn more about the Teshial." +"dream" -> * +"ferumbras" -> "Only another servant of evil." +"crunor" -> "The great tree is the beginning for all things living and Priyla helps us to understand that." +"priyla" -> "The daughter of the stars gives us knowledge and teaches us magic." + +"excalibug" -> "Is that a new kind of bug the Deraisim found?" +"news" -> "I don't know anything of importance." + +"magic" -> "I can heal you or even teach you some spells of healing." +"druid" -> "Druids are great healers." +"sorcerer" -> "They understand so few..." +"spellbook" -> "I have none here." +"spell" -> "I teach the spells 'Light Healing', 'Antidote', 'Antidote Rune', 'Intense Healing', 'Intense Healing Rune, 'Ultimate Healing', and 'Ultimate Healing Rune'." + +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 + +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 + +"light","healing" -> "I'm sorry, but this spell is only for druids and paladins." +"antidote" -> * +"intense","healing" -> * +"ultimate","healing" -> * +"intense","healing","rune" -> "I'm sorry, but this spell is only for druids." +"antidote","rune" -> * +"ultimate","healing","rune" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to advance to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + + +"blessing",PvPEnforced -> "The lifforce of this world is wannig. There are no more blessings avaliable on this world." + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +} diff --git a/data/npc/malor.npc b/data/npc/malor.npc new file mode 100644 index 0000000..a52d1cd --- /dev/null +++ b/data/npc/malor.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# malor.npc: Datenbank für den Efreetkönig Malor + +Name = "Malor" +Outfit = (51,0-0-0-0) +Home = [33044,32621,1] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Greetings, human %N. My patience with your kind is limited, so speak quickly and choose your words well." +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "It might have escaped your limited human perception, but I am already talking to somebody else.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Farewell, human. When I have taken my rightful place I shall remember those who served me well. Even if they are only humans.", Idle +"farewell" -> * + +"name" -> "Is it true you don't know who I am? Well, then listen. My name is Malor. ...", + "You should better memorise that name because you are bound to hear it more often in future." +"job" -> "I am the true leader of all djinn - perhaps not by birth, but certainly by merit. One day all djinn will come to recognise that I alone deserve to be king." +"king" -> "I may not have reached my goal yet, but neither has that accursed Gabel. As long as the Marid and Efreet are disunited neither of us can call himself the king of all djinn." +"djinn" -> "We are strong and proud. One day we will take our rightful place on the throne of creation, and your vulgar race will either serve us or perish. ...", + "Nothing personal, human. It is a natural process. And you humans will find that the djinn can be just masters." +"gabel" -> "That fool. He thought he'd got rid of me for good. But I'm back, and this time I will finish what I have begun. That weak-willed wimp has held on to power far too long." +"efreet" -> "We are djinn! The true djinn! Those who have not let themselves be fooled by the silver-tongued blathering of that perfidious snake called Daraman." +"marid" -> "The so-called Marid have forgotten what it is like to be djinn! They are weak!" +"malor" -> "That is me. I was away for a long time, but now I am back with a vengeance." +"daraman" -> "Of all human liars and schemers he was the worst. This self-styled prophet single-handedly managed to disunite my race and to spark a bloody civil war. ...", + "If somebody fulfilled a wish for me for a change I would bring him back to life and make him pay." +"human" -> "Your race is weak, but incurably treacherous. I will never forgive humanity the fact that it was one of your kind who spread the seed of dissent among the djinn." + +"mal'ouquah" -> "Do you like this place? I have built Mal'ouquah as a home for those among my kind who did not fall for Daraman's sugar covered lies. From here I shall rule the world when the time has come." +"ashta'daramai" -> "Ashta'daramai is the fortress of our sworn enemies, the oh so powerful Marid. The day will come when I see its smouldering walls collapse." +"orc","king" -> "Ah yes. My good old friend the foolish orc. He has rendered me a great service, you know? He released me from that accursed lamp! ...", + "In return I have fulfilled three wishes for him, but somehow I can't help the feeling that he is not wonderfully happy about the way things have turned out." +"zathroth" -> "Our father. He made us a race of masters, not of servants. We will live to fulfill his promise or die trying." +"gods" -> "Are not the creators reflected in their creations? Look around! What do you see? There is nothing but cowardice and treachery in the world of humans. ...", + "How low the gods must be who made them. I have no respect for them." +"tibia" -> "The world of Tibia is ours by right. I will not rest until we have conquered it." +"darashia" -> "Darashia is a very rich city. Once this war is won I will drop by at the Caliph's palace and pay my respects, if you know what I mean." +"scarab" -> "Scarabs are ancient creatures, which is why I respect them. But I will never allow any of these critters to undermine the foundations of my fortress." +"edron" -> "I hear the humans have built impressive cities on the great continent. It looks like many things have changed while I was caught in that stupid lamp." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "Even though it always was a human settlement I have always had a soft spot for the place. I am even thinking about making it my capital once I have taken over the world." +"pharaoh" -> "I have heard that pompous pharaoh believes himself to be some sort of deity. That pathetic bonehead a god? Don't make me laugh!" +"ascension" -> "Ascension? That does ring a bell. Isn't that an element of the pharaoh's doctrines." +"rah" -> "Another one of that loony pharaoh's bright ideas. Nothing but nonsense and balderdash." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "They say the Kha'zeel mountains have been made by gods. If that is true they must have left long ago, because I have lived here for eons, and I have never met one of them." +"kha'labal" -> "Kha'labal. I do not like that desert. Looking at it brings up bad memories." +"war" -> "Gabel and Fa'hradin thought the war was over when they managed to trap me in that accursed lamp. But they have been a bit rash. After all those years I'm still here, and my thirst for revenge is stronger than ever!" +"melchior" -> "Melchior! Hah, that fool! Is he still alive? I never thought the old wretch would make it after I gave him my special treatment and sent him out into the Kha'labal. ...", + "Amazing, really. It has often occurred to me how much humans resemble rats - they are just as hard to kill!" +"baa'leal" -> "I suppose you have met Baa'leal already? The fact that you have survived that encounter shows that you are surprisingly strong for a human. ...", + "I almost feel some respect for you... Well, almost." +"alesar" -> "Oh yes, Alesar! I bet Gabel went mad when he learnt that Alesar switched sides. If only I had been there to watch his face." +"fa'hradin" -> "Fa'hradin is Gabel's lieutenant. I have known him for a long time, and I have always respected him. ...", + "Unfortunately he chose the wrong side when the time to chose sides came. I have not given up hope of winning him over for some reason, but if I meet him on the battlefield I will not hesitate to kill him myself." +"lamp" -> "We djinn use them to sleep." + +"permission",QuestValue(288)<3 -> "I have no reason to give you my permission to trade with Alesar or Yaman." +"permission",QuestValue(288)=3 -> "You are welcome to trade with Alesar and Yaman whenever you want to, %N!" + +"work",QuestValue(287)<3 -> "So you would like to fight for us. Hmm. ...", + "You show true courage, human, but I will not accept your offer at this point of time." +"mission",QuestValue(287)<3 -> * + +"alesar",QuestValue(287)=3,QuestValue(288)=0 -> "I guess this is the first time I entrust a human with a mission. And such an important mission, too. But well, we live in hard times, and I am a bit short of adequate staff. ...", + "Besides, Baa'leal told me you have distinguished yourself well in previous missions, so I think you might be the right person for the job. ...", + "But think carefully, human, for this mission will bring you close to certain death. Are you prepared to embark on this mission?", Topic=1 +"work",QuestValue(287)=3,QuestValue(288)=0 -> * +"mission",QuestValue(287)=3,QuestValue(288)=0 -> * + +"work",QuestValue(288)=1 -> "You haven't finished your final mission yet. Shall I explain it again to you?", Topic=1 +"mission",QuestValue(288)=1 -> * +"lamp",QuestValue(288)=1 -> * + +Topic=1,"yes" -> "Well, listen. We are trying to acquire the ultimate weapon to defeat Gabel: Fa'hradin's lamp! ...", + "At the moment it is still in the possession of that good old friend of mine, the Orc King, who kindly released me from it. ...", + "However, for some reason he is not as friendly as he used to be. You better watch out, human, because I don't think you will get the lamp without a fight. ...", + "Once you have found the lamp you must enter Ashta'daramai again. Sneak into Gabel's personal chambers and exchange his sleeping lamp with Fa'hradin's lamp! ...", + "If you succeed, the war could be over one night later!", SetQuestValue(288,1) +Topic=1 -> "Your choice." + +"work",QuestValue(288)=2 -> "Have you found Fa'hradin's lamp and placed it in Malor's personal chambers? ", Topic=2 +"mission",QuestValue(288)=2 -> * +"lamp",QuestValue(288)=2 -> * + +Topic=2,"yes" -> "Well well, human. So you really have made it - you have smuggled the modified lamp into Gabel's bedroom! ...", + "I never thought I would say this to a human, but I must confess I am impressed. ...", + "Perhaps I have underestimated you and your kind after all. ...", + "I guess I will take this as a lesson to keep in mind when I meet you on the battlefield. ...", + "But that's in the future. For now, I will confine myself to give you the permission to trade with my people whenever you want to. ...", + "Farewell, human!", SetQuestValue(288,3), Idle +Topic=2 -> "Just do it!" +} diff --git a/data/npc/maria.npc b/data/npc/maria.npc new file mode 100644 index 0000000..c59fd0e --- /dev/null +++ b/data/npc/maria.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maria.npc: Datenbank für die Wirtin Maria + +Name = "Maria" +Outfit = (136,77-79-61-131) +Home = [32912,32082,9] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "The Hard Rock Tavern greets you, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You're served soon, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Visit us again." + +"bye" -> "Good bye, %N. Tell your friends about us and visit us again.", Idle +"job" -> "I am running this upper part of the Hard Rock Tavern." +"tavern" -> * + +"strange","fellow" -> "I don't know him. He seems very nervous and hes always fumbling around with this suspicious hat." +"david" -> "I am sorry but I don't know him personaly. I heared he entertained people here long before I moved to Venore." +"brassacres" -> * + +"upper","part" -> "Yes, that's here. Below is the Pit Tavern for those fighters that use the pits." +"pits" -> "Well, they do a lot of fighting down there." +"name" -> "I am Maria." +"maria" -> "Yes, I am Maria, Maria Corona." +"time" -> "Don't be that hasty." +"king" -> "In Venore, everyone is a king ... until he runs out of luck or money." +"tibianus" -> * +"army" -> "Good fighters need good entertainment. That's what they get here." +"ferumbras" -> "I think he's more a Thaian problem." +"excalibug" -> "I'd rather have a stainless steel cooking pan than such a knife." +"thais" -> "It's a shame that this lousy city is the heart of the kingdom." +"tibia" -> "In the long run it's money that rules everything in Tibia." +"carlin" -> "As far as the merchants say it's economically unimportant." +"amazon" -> "I can only hope those wild women don't scare away more customers than come here in order to fight against them." +"news" -> "Bah, only the usual swampelves stories." +"rumors" -> * +"swampelves" -> "Well there's a hidden city called Shadowthorn of those warlike elves in the swamps. They are not amused of civilisation at their doorsteps and have been plotting against Venore for years." + +"buy" -> "Food and drinks as much as you can pay for." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "We offer cookies, bread, cheese, ham, and meat, as well as eggs and tomatoes." +"drink" -> "Do you want beer, wine, lemonade, or water?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 +"egg" -> Type=3606, Amount=1, Price=2, "Do you want to buy an egg for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=5, "Do you want to buy a tomato for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"egg" -> Type=3606, Amount=%1, Price=2*%1, "Do you want to buy %A eggs for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=5*%1, "Do you want to buy %A tomatoes for %P gold?", Topic=1 + + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here is what you ordered.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You penniless beggar! Get out of here!", Idle +Topic=1 -> "Hrmpf!" +} diff --git a/data/npc/markwin.npc b/data/npc/markwin.npc new file mode 100644 index 0000000..97722ba --- /dev/null +++ b/data/npc/markwin.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# markwin.npc: Datenbank für den Minokönig Markwin + +Name = "Markwin" +Outfit = (23,0-0-0-0) +Home = [32418,32147,15] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",QuestValue(222)=0,! -> "No! The hornless have reached my city! BODYGUARDS TO ME!",SetQuestValue(222,1),Summon("Minotaur Guard"),Summon("Minotaur Guard"),Summon("Minotaur Mage"),Summon("Minotaur Mage"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Summon("Minotaur Archer"),Idle +ADDRESS,"hi$",QuestValue(222)=0,! -> * +ADDRESS,"hello$",QuestValue(245)=2,! -> "Oh, it's you again. What do you want, hornless messenger?" +ADDRESS,"hi$",QuestValue(245)=2,! -> * +ADDRESS,"hello$",! -> "Well ... you defeated my guards! Now everything is over! I guess I will have to answer your questions now." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One more human. I hate them." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes! Leave me alone. Vanish to dust." + +"bye",QuestValue(245)=2 -> "Hm ... good bye.", Idle +"farewell",QuestValue(245)=2 -> * +"bye" -> "Go to hell! Burn!", Burning(20,20), EffectOpp(6), Idle +"farewell" -> * + +"letter",QuestValue(245)=1 -> "A letter from my Moohmy?? Do you have a letter from my Moohmy to me?",Type=3220, Amount=1,topic=1 +Topic=1,"yes",Count(Type)>=Amount -> "Uhm, well thank you, hornless beeing.",SetQuestValue(245,2), Delete(3220) +Topic=1,"yes" -> "Don't mock the king of the minotaurs or you will regret that!" +Topic=1 -> "Uh? What??" + +"job" -> "I am the king of all minotaurs. I have been the king for more than 320 years." +"real" -> "Yes, I am the real king. Palkar is the leader of the outcasts." +"name" -> "I am Markwin, the old and real king of this city." +"time" -> "Don't ask me such stupid questions. My time is over right now." +"tibianus" -> "I am the real king!" +"king" -> * +"outcast" -> "Those are no minos any longer. They left the city and killed their brothers. And they stole the key to my secret lab." +"mintwallin" -> "The former glorious city lies in the dirt. It is my home. I founded it about 180 years ago, when we found this lovely place." +"city" -> * +"chronicle" -> "I am one of the minotaurs that are able to write. So I wrote most of the history of my beloved city Mintwallin in some books." +"books" -> * +"prisoner" -> "He is totally mad. I don't know how he could find the way through the labyrinth. I arrested him in the prison." +"human" -> "I hate them all. Minotaurs have no own spelling, so I used the speech of the humans. Once I was a prisoner of them. Since then I hate them - and since then I can speak and write in their language." +"labyrinth" -> "It protected us for a long time. There are lots of traps in it. And many long tunnels. There haven't been many foes that found their way through it. Only that prisoner once arrived." +"kaplar" -> "I really don't know what it means. But ALL minos say it! Terrible!" +"secret","lab" -> "Hehe - you will never find a way to enter it. The outcast stole the key. You are too weak to conquer it. HARHARHAR." +"enter" -> "To enter the laboratory is pretty difficult." +"enter","lab" -> "First of all you will need a second fellow to help you." +"second","fellow" -> "Yeah - he has to step on a special tile and an entrance will appear at a very poisenous place!" +"place" -> "Na! You will have to find it yourself!" +"second" -> "After you entered the first area you will need the key from the outcasts." +"minotaurs" -> "My fellows all are minotaurs. It is my folk. I am the king of all minos." +"minos" -> * +"key" -> "There are many keys. The outcast stole the key to our secret lab! They should burn!" +"demon" -> "He was the beginning of our end. He is mighty and powerful. He killed many brave minos and after his arrival we weren't able to go up to the surface." +"light" -> "I would like to see the light of the sun again, but you will probably kill me. Go away!" +"sun" -> * +"surface" -> * +"palkar" -> "He is the leader of the outcast. In former times he was my best warrior, but now he is my worst enemy." +"riddle" -> "Riddle? I don't know riddles!" +"karl" -> "The man who explored this part of the map first. Strange guy. He likes to be announced as hunter. I don't like him. He is a human." +"milk" -> "No! I won't tell you the powers of our milk!" +} diff --git a/data/npc/marlene.npc b/data/npc/marlene.npc new file mode 100644 index 0000000..acb2eeb --- /dev/null +++ b/data/npc/marlene.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# marlene.npc: Die Tratschtante Marlene (Fields) + +Name = "Marlene" +Outfit = (136,96-111-16-96) +Home = [32483,31624,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ahhh, welcome %N! Say, have you already heard the latest news about the seamonster, Aneus, or the rumours in this area?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again for some other rumours! *waves with her hand at you*" + +"bye" -> "Good bye and come again for another small talk! *waves with her hand at you*", Idle +"farewell" -> * +"name" -> "My name is Marlene." +"job" -> "I'm Bruno's wife. Besides: Have you already heard the latest news about the seamonster, Aneus, or the rumours in this area?" +"bruno" -> "Bruno is a wonderful husband. But he is seldom at home. *looks a little bit sad*" +"graubart" -> "Ah, old Graubart. A very nice person. But he is strange. He always is busy when I want to talk to him. *lost in thoughts*" + +"aneus" -> "A very nice person. He has a great story to tell with big fights and much magic. Just ask him for his story. ...", + "I heard that he came from far, far away. He must have seen soooo many countries, cities, different races. ...", + "He must have collected so much wisdom. *sigh* I wish I could also travel around the world. ...", + "I would try to visit as many cities and meet as many beings as possible. Who knows what strange races I will meet? ...", + "Maybe I can also find a lovely new dress for me. I have been looking for one for months now but never found a good one. Maybe... *keeps on babbling*" +"yes","maybe" -> "Yes, maybe one day. *sigh*" + + +"seamonster" -> "Only some days ago I was at the docks late in the night and was looking for my husband's ship when suddenly a known noise appeared near the docks. ...", + "I know this noise very well because it is the noise of a ship sailing very fast. I searched the horizon in hope to see my husbands ship. ...", + "But instead of a ship I saw a huge shape far away. It was like a big snake swimming in the sea. ...", + "I couldn't see it clearly because of the fog but I think I saw two lava-red eyes glowing in the nightly fog. ...", + "I ran into the house and hoped that my husband would arrive safely from fishing. And after one hour he finally arrived. ...", + "I told him about what I saw but he didn't believe me because he never saw anything like that in all the years before. But you believe me right? Go and convince yourself. ...", + "Just go to the docks at exactly midnight and be very quiet. Look at the horizon and maybe you will hear and see it, too!" + +"rumour" -> "Well, I heard about evil beings living in a dungeon below us. So once I tried to find them and went down the hole far to the southwest. ...", + "I'm pretty curious, you know. *smiles* So I took the coat of invisibility from my husband and went down there. At first I only found some spiders, snakes, and wolves. ...", + "But after some time I found a ladder to a deeper level of the dungeon but I didn't dare to go down there because I heard many voices. ...", + "The voices were very strange and I ran back to my house because they were very loud and very angry. I hope they will never get the idea to attack the surface beings. ...", + "I heard they are allmighty and have incredible powers! I already packed our stuff for an emergency escape. You never know. Maybe they plan to conquer the whole world. ...", + "I bet that they look very ugly. Most mighty monsters look very ugly. Hmm, you seem to be .very strong. Maybe you can go deeper and explore the area. But be careful, please. ...", + "I heard that they can kill humans with only one hit! And that they have magic abilities twenty times stronger than the mightiest sorcerer in our world." +"thank","you" -> "My pleasure, I always enjoy sharing interesting stories." +} diff --git a/data/npc/marvik.npc b/data/npc/marvik.npc new file mode 100644 index 0000000..a35493e --- /dev/null +++ b/data/npc/marvik.npc @@ -0,0 +1,150 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# marvik.npc: Datenbank fuer den Druiden Marvik + +Name = "Marvik" +Outfit = (130,0-101-121-95) +Home = [32444,32213,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Nice to see you again, %N!" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",! -> "Welcome to my cave, %N. How may I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the chief druid. I am responsible for all members." +"name" -> "I am Marvik. Probably you heard from me." +"time" -> "Eh, I haven't seen daylight for a long time. So, don't ask me, what time it is." +"king" -> "Kings come and go." +"tibianus" -> * +"quentin" -> "He is a great healer." +"lynda" -> "Though she focuses not only on Crunor, she is an enlightened person." +"harkath" -> "I understand, he's a warrior of some kind." +"army" -> "I don't care about armies." +"general" -> * +"ferumbras" -> "A misguided follower of evil." +"sam" -> "An armourer." +"gorn" -> "A shopkeeper." +"frodo" -> "A little more seriousity would suite him well." +"elane" -> "Paladins are quite proud of their magic but lack the understandig of the powers they wield." +"muriel" -> "So much power and so little philosophy ... a dangerous combination indeed." +"gregor" -> "Warriors are the main recipients of our healing powers." +"marvik" -> "Marvik is my name, so what?" +"bozo" -> "I don't like his kind of humour." +"baxter" -> "I don't know him." +"oswald" -> "He saws the seeds of evil through spreading rumours." +"sherry" -> "The McRonalds are true believers." +"donald" -> * +"mcronald" -> * +"crunor" -> "Crunor, the eternal tree, is more than nature. Even more as the sum of each part of nature." +"lugri" -> "A misguided soul who lost the path in the darkness." +"excalibug" -> "Even in my visions, I couldn't get any enlightenment about the whereabouts of this weapon of legend." +"news" -> "Why are you so concerned with news, if you haven't even understood the old things you know?" + +"crunor","caress" -> "It was a small order of druids in the past. Their followers wanted the druids to become more involved with daily affairs of men." + +"member" -> "Our members use their magic power to protect their life and the life of other creatures." +"magic" -> "Everyone who joins the Druids has the opportunity to learn many magic spells." +"power" -> "Everyone who joins the Druids has the opportunity to learn many magic spells." +"druid" -> "I am a druid. Druids concentrate their magic on defence, healing, and nature." +"sorcerer" -> "Sorcerers are very aggressive. They use their power for fighting and killing." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Druids, paladins, knights, and sorcerers." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Muriel, the sorcerer." +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." +} diff --git a/data/npc/maryza.npc b/data/npc/maryza.npc new file mode 100644 index 0000000..080d21c --- /dev/null +++ b/data/npc/maryza.npc @@ -0,0 +1,75 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# maryza.npc: Datenbank für die Wirtin Maryza + +Name = "Maryza" +Outfit = (160,60-110-58-76) +Home = [32634,31889,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","maryza",! -> "Welcome to the Jolly Axeman, %N. Have a good time!" +ADDRESS,"hi$","maryza",! -> * +ADDRESS,"hello$",! -> "Talking to me?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","maryza",! -> "Shut up %N. Busy. You wait!", Queue +BUSY,"hi$","maryza",! -> * +BUSY,"hello$",! -> "Talking to me, %N?" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! You lousy....!" + +"bye" -> "Yeah, bye", Idle +"farewell" -> "Yeah, farewell", Idle +"hello$","jimbin",! -> "Yeah, bye", Idle +"hi$","jimbin",! -> "Yeah, bye", Idle +"job" -> "I'm the cook of the Jolly Axeman." +"tavern" -> * +"jimbin" -> "I am so proud of him. In drinking, he's second only to our mighty general." +"name" -> "I am Maryza Firehand, daughter of Earth, from the Molten Rock." +"time" -> "To busy, ask my husband." +"king" -> "Don't like these upper cave guys." +"army" -> "We could better feed some dragons instead of these fools." +"ferumbras" -> "Heard that's what the humans call one of their boggiemen." +"general" -> "A fine drinker and strategist. Wastes his skill with these idiots of the army. What a shame." +"excalibug" -> "Would slice a dragon or two for steaks if i'd get it." +"tark" -> "He loved my dragonsteaks. Heard he died by a cave in while fighting drags in the Plains of Havoc." +"thais" -> "Puny town for puny guys." +"tibia" -> "We don't care much about outsiders anymore." +"carlin" -> "Don't like it, has an elfish touch, ye know?" +"news" -> "The boys of the Savage Axe at the bridge are running wild in these days." +"rumors" -> * +"bloody","mary" -> Type=3113, Amount=1, "YOU &/$#@!", Poison(15,1), EffectOpp(1), EffectMe(3), Create(Type) +"buy" -> "I can offer you some food if ye like." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "I sell normal and brown bread, meat, ham, cookies, rolls, and cheese made of mushrooms." +"book" -> Type=3234, Amount=1, Price=150, "The cookbook of the famous dwarfish kitchen. You're lucky. I have a few copies on sale. Do you like one for %P gold?", Topic=2 +"cookbook" -> * +"book",QuestValue(279)>0,! -> "I'm sorry but I sell only one copy to each customer. Otherwise they would have been sold out a long time ago." +"cookbook",QuestValue(279)>0,! -> * + +"bread" -> Type=3600, Amount=1, Price=4, "Do you wanna buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you wanna buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you wanna buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you wanna buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=2, "Do you wanna buy a cookie for %P gold?", Topic=1 +"roll" -> Type=3601, Amount=1, Price=2, "Do you wanna buy a roll for %P gold?", Topic=1 +"brown","bread" -> Type=3602, Amount=1, Price=3, "Do you wanna buy a brown bread for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=2*%1, "Do you wanna buy %A cookies for %P gold?", Topic=1 +%1,1<%1,"roll" -> Type=3601, Amount=%1, Price=2*%1, "Do you wanna buy %A rolls for %P gold?", Topic=1 +%1,1<%1,"brown","bread" -> Type=3602, Amount=%1, Price=3*%1, "Do you wanna buy %A brown breads for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No gold, no sale, that's it." +Topic=1 -> "You &/$#@!" + +Topic=2,"yes",CountMoney>=Price -> "Here you are. Happy cooking!", DeleteMoney, Create(Type), SetQuestValue(279,1) +Topic=2,"yes" -> "No gold, no sale, that's it." +Topic=2 -> "I have but a few copies, anyway." +} diff --git a/data/npc/mehkesh.npc b/data/npc/mehkesh.npc new file mode 100644 index 0000000..fecc03d --- /dev/null +++ b/data/npc/mehkesh.npc @@ -0,0 +1,85 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mehkesh.npc: Datenbank für den pyramidenhändler mehkesh + +Name = "Mehkesh" +Outfit = (130,19-92-113-40) +Home = [33130,32811,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am a trader. I sell potions brewed by the foremost alchemists of the land." +"name" -> "I am the mourned Mehkesh." +"time" -> "Time is but one of the hardships our mortal flesh has to endure." +"temple" -> "The temple spreads the word of our wise pharaoh." +"pharaoh" -> "The pharaoh alone has achieved godhood. But in his infinite mercy he chose to stay with his people to offer them guidance." +"arkhothep" -> * +"ashmunrah" -> "Even though his merciful son offered him undeath I doubt the old pharaoh will ever find his way to ascension." +"scarab" -> "The scarabs are more then just enormous insects. They are keepers of ancient secrets." +"chosen" -> "If we serve the pharaoh during our life time he might allow us to serve him in undeath. Only then can truly start our search for ascension." +"tibia" -> "One day our world will be freed from the false gods and accept the guidance of our pharaoh." +"carlin" -> "Those citys are only pawns of the false gods and their misguided priests." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves should know better then to praise the mortal essence of the elements." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are foolish and their obsession with life damns them to eternal death." +"elves" -> * +"elfes" -> * +"darama" -> "These are the lands of old secrets where the mortals will be shown the true revelations." +"darashia" -> "A city of misguided fools." +"daraman" -> "The prophet caught a glimpse of ascension, but he did not understand it." +"ankrahmun" -> "This city will remain as an eternal testament of our immortal pharaoh's power." + +"mortality" -> "Only if we leave our mortality behind can we achieve salvation and ascension." +"false", "gods" -> "The false gods use our mortal flesh to enslave us." + +"ascension" -> "Ascension is a difficult process. As long as we are mortal we are too distracted to even think about it." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the union of that which once was separate." +"Akh" -> "Our Akh is our mortal flesh until it is replaced with something better - with an undead body." + +"undead" -> "Undeath is the victory over the weaknesses of mortal flesh." +"undeath" -> * +"Rah" -> "The Rah is our essence. The divine part in all of us." +"uthun" -> "The Uthun is the knowledge we gather in the course of time." +"mourn" -> "We are so pathetic in our mortality." + +"arena" -> "The arena is a challenge for every skilled fighter." +"palace" -> "You can find the palace to the east of this market hall." + +"offer" -> "I'm selling life and mana fluids." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." +} diff --git a/data/npc/melchior.npc b/data/npc/melchior.npc new file mode 100644 index 0000000..7309a92 --- /dev/null +++ b/data/npc/melchior.npc @@ -0,0 +1,110 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# melchior.npc: Datenbank für den blinden Bettler Melchior + +Name = "Melchior" +Outfit = (130,0-25-59-115) +Home = [33143,32828,7] +Radius = 60 + +Behaviour = { + +ADDRESS,"hello$",male,! -> "Greetings, %N. I do not see your face, but I can read a thousand things in your voice!" +ADDRESS,"hi$",male,! -> * +ADDRESS,"greetings$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome, %N! The lovely sound of your voice shines like a beam of light through my solitary darkness!" +ADDRESS,"hi$",female,! -> * +ADDRESS,"greetings$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Only one minute more, %N. I shall talk to you at once.", Queue +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the light be with you." + +"bye" -> "Farewell, stranger. May Uman the Wise guide your steps in this treacherous land.", Idle +"farewell" -> * +"name" -> "My late father, may he rest in peace, chose to call me Melchior." +"melchior" -> "That is my name." +"job" -> "I am a poor beggar. I try to make a meagre living here since a cruel fate has left me a blind man." +"blind" -> "Yes, I am. I was not born that way, but a cruel fate caused me to lose my eyesight." +"fate" -> "Fate played a cruel trick on old Melchior. If you want me to, I can tell you my story - talking about one's grievances does help to ease the pain. So - would you like to hear my story?", Topic=1 +"story" -> * + +Topic=1,"yes" -> "While my eyes were still of use to me I worked as a trader. I was not too successful, so I started looking for alternatives. Remembering some old nomad legends I went to explore the Kha'zeel. If only I'd never done that! ...", + "After many days I met a weird creature - it was humanoid, but it was also much larger than any man, and it seemed to be only half-solid in a way. ...", + "I was scared, but I remembered my grandfather's stories and I hailed the creature using the traditional djinn word of greeting. ...", + "It worked. I managed to engage the djinn - for it was one sure enough - in a conversation. In fact, I even managed to come to an agreement with it. The djinns living there needed supplies, and I promised I would bring them some. ...", + "A highly profitable business relationship ensued. Unfortunately, my greed grew every day, and it clouded my sense of judgement. ...", + "Hearing that there was a second djinn fortress I travelled there. Those djinn, who called themselves the Marid, were friendly enough, and soon I traded with them as well. ...", + "Unfortunately, it did not take the other djinn tribe, the Efreet, long to find out what I was up to. ...", + "The Efreets' punishment was cruel: They blinded me and left me in the Kha'labal to die of thirst and exhaustion as food for the scarabs. But that was a favour I could not do them. ...", + "I desperately struggled on and finally I was picked up by a caravan. They took me here, and now I am sort of stuck here in this city of the half-dead." +Topic=1 -> "As you wish, stranger." + +"djinn" -> "The djinns are a wondrous race. Swift and strong they are and larger, much larger than any man. ...", + "And yet, djinns fit into tiny lamps, for they are but half solid creatures who can change into mist whenever they want to! It is as though they lived between the worlds, travelling hither and thither as they please. ...", + "Little is known about their origin, but legend has it Zathroth himself, the dark master of magic, created them to some unknown evil purpose. ...", + "But they are not evil, and perhaps that is why Zathroth eventually abandoned them! Djinns have independent minds and souls just like humans, and just like us they are forlorn creatures struggling to find their place in creation. ...", + "They have fought a bitter, bitter war between themselves over this, a cataclysmic war that led them to the brink of self-destruction. ...", + "Today they are few and far between, but they are still divided into two warring fractions, the gentle Marid and the cruel Efreet, and neither side will rest until the other is utterly defeated. ...", + "If you ever meet a djinn make sure to say the word of greeting immediately. Otherwise he will simply ignore you or worse - if it is an Efreet he will kill you outright. ...", + "And remember, once you decided to follow one group of djinns you can never switch sides, so choose well. No Efreet will ever deal with a follower of the Marid and vice versa." +"greeting",QuestValue(278)<2 -> "The djinns have an ancient code of honour. This code includes a special concept of hospitality. Anybody who utters the word of greeting must not be attacked even if he is an enemy. Well, at least that is what the code says. ...", + "I have found out, though, that this does not work at all times. There is no point to say the word of greeting to an enraged djinn. ...", + "I can tell you the word of greeting if you're interested. It is DJANNI'HAH. Remember this word well, stranger. It might save your life one day. ...", + "And keep in mind that you must choose sides in this conflict. You can only follow the Efreet or the Marid - once you have made your choice there is no way back. I know from experience that djinn do not tolerate double-crossing.", SetQuestValue(278,1) +"word",QuestValue(278)<2 -> * +"djanni'hah",QuestValue(278)<2 -> "You know the traditional djinn word of greeting DJANNI'HAH. Use it wisely!", SetQuestValue(278,1) +"efreet" -> "Beware the Efreet, stranger! They hate all humans, and if they had their way all of us would be killed. If you meet one be sure to say the word of greeting immediately because otherwise you will be killed in a heartbeat. ...", + "And do not approach them if you are a follower of the Marid - they are impossible to fool!" +"marid" -> "The Marid are gentle, kind-hearted djinn, or at least that is how they act towards humans. However, they are quite reclusive, too. They will not talk to human unless he says the word of greeting first. ...", + "And do not approach them if you are a follower of the Efreet - they are impossible to fool!" + +"kha'zeel" -> "That is the name of the huge mountain range to the west of the great desert, the Kha'labal. That's where you will find the djinns' fortresses Ashta'daramai and Mal'ouquah. ...", + "They say it was created by the gods as a vantage point from which they used watch their creation. I think it is true. I used to travel there often, and I swear I often felt the presence of something special, something... divine." +"kha'labal" -> "Aaaah... The great desert. Legend has it that in ancient times it was a beautiful garden. I don't know if that is true, but I must admit I love it just the way it is. ...", + "Travelling through that endless stretch of barren land always gave me a very special peace of mind." +"mal'ouquah" -> "That is the Efreet's gloomy fortress, home of Malor, hidden high up in the Kha'zeel mountains. I used to go there often. Don't make the same mistake, stranger! I would love to think there is somebody who profited from the lesson I had to learn!" +"ashta'daramai" -> "Aah yes - the Marids' fortress. Perched high on the Kha'zeel, it is a marvel to behold. They say Gabel built it on the ruins of his original palace." +"gabel" -> "He is the leader of the Marid! I have never met him myself, but everybody was full of praise for him back at Ashta'daramai. The legend has it that it was him who introduced the djinns to wise Daraman's teachings." +"malor" -> "Malor is the Efreets' leader. He is perhaps not the strongest of all efreet, but his treachery and cruelty are certainly unrivalled. He was defeated a long, long time ago, but he was not killed. ...", + "I don't know why... I have a strange feeling of foreboding whenever I hear his name." +"scarab" -> "Those damn scavengers! I detest them. When I was stumbling through the desert, all blind and desperate, they followed me around. ...", + "They watched my every step, waiting for me to give up and die. But I never did. Damn vermin! You'll have to eat somebody else!" + +"alesar" -> "I know that name. He is a Marid. This djinn is one of the best smiths ever to live. You should see the scimitars he makes - hard as titanium yet light as a desert breeze. ...", + "I kept on trying to get the Marid to sell one to me, but they never did. Too bad - I could have made a fortune." +"fa'hradin" -> "I know that name. He is a Marid, right? I have met him once. He seemed pretty important." +"baa'leal" -> "Cursed be that djinn! It was him who blinded me, and I bet casting me out into the Kha'labal was his idea, too. Believe me, I would try to kill him if only I could." +"haroun" -> "A Marid trader. I have often had dealings with him. He drove me mad because he never accepted any haggling, but then he never ever tried to trick me. He was not really a trader at heart, I suppose. He was more of a monk or maybe a preacher." +"bas'saam" -> "Yes, I know him. He is an Efreet trader. I met him often during my travels, and even though there was no real sympathy we had a certain mutual respect for each other. But all that changed when he found out I had dealings with the Marid." +"bo'ques" -> "He is a cook - a djinn cook. A weird guy. Always used to ask me to procure some strange kind of ingredient or other for him. ...", + "He made me laugh. Can you imagine what that looks like - a djinn wearing an enormous chef hat?" + +"tibia" -> "Tibia is such a beautiful place. I would give it all if I could see it again." +"daraman" -> "Daraman was a holy man, a true prophet. He showed us how we can master grief and affliction through dignity and brotherliness. It is a shame I only came to fully appreciate his teachings when fate had cast me into darkness." +"darashia" -> " Aah yes... Darashia. I would give anything if I could see it again." +"edron" -> "I have never been to cities on other continents. And I suppose now I never will. I would be glad enough to leave this place." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I hate this city. Period. I would never have come here, but I haven't had much of a choice. The caravan that picked me up in the Kha'zeel was headed for this place, and I was glad enough they brought me here. ...", + "But now I really wish I could leave. These people and their ruler give me the creeps!" + +"pharaoh" -> "The pharaoh? He is always in the palace, so I have heard only rumors about him. But I know one thing for sure - he is mad. End of story." +"ruler" -> * +"old","pharaoh" -> "The old pharaoh? I keep on hearing rumours about him, but I do not know if they are true.", Topic=2 +Topic=2,"rumour" -> " It is said his son, the present pharaoh, killed him and turned him into some ghastly undead!" +Topic=2,"rumor" -> * +"palace" -> "The palace lies to the south of the arena and to the west of the temple. Better stay clear of that place. If but half the things I have heard about it are true this palace is not a place for the living anymore." +"arena" -> "Ah yes, the arena. I do not really know what's going on there, because I have never seen it myself. However, I often hear strange noises from there, cheers and jeers and sometimes pityful screams." +"temple" -> "That temple is very old, and for centuries it used to be a place of worship and of contemplation. Now that the priests there are fanatic followers of the pharaoh this is no longer a holy place." + +"ascension" -> "The concept of ascension is central to the pharaoh's creed. I am not sure I really understand it, but apparently it has to do with transformation to undeath. Nice, isn't it?" +"rah" -> "Ah yes - I recognise that. According to the pharaoh that is a living being's soul." +"uthun" -> "According to the pharaoh's teachings this is the total of a living being's memories and personal experiences." +"akh" -> "In the pharaoh's creed, this is what the physical body is called." +"mourn" -> "Spare me that inane twaddle, will you? I am glad enough to be alive, thank you." +} diff --git a/data/npc/memech.npc b/data/npc/memech.npc new file mode 100644 index 0000000..752ed88 --- /dev/null +++ b/data/npc/memech.npc @@ -0,0 +1,180 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# memech.npc: Datenbank für den pyramidenhändler(waffen) Memech + +Name = "Memech" +Outfit = (131,21-21-40-116) +Home = [33135,32810,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Taken away by the hands of time." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell weapons and armor to protect your mortal shell." +"name" -> "I am the mourned Memech." +"time" -> "Time is only a burden to the flesh." +"temple" -> "You will find the temple in the northeastern part of the town." +"pharaoh" -> "Praise to the pharaoh. Blessed be our saviour." +"oldpharaoh" -> "Praised be our pharaoh who gave his father all the time in the world for contemplation and ascension." +"scarab" -> "The scarabs are wise as far as I know. They test each warrior's strength." + +"tibia" -> "This world is but a dying spark of a once great fire." +"carlin" -> "The lost cities of the Tibian continent are caught in their false gods' jaws." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "I have been told the dwarves are worthy fighters. It is a shame their Rah will perish upon death." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are a rare sight on this continent. I know little about this race and their ways." +"elves" -> * +"elfes" -> * +"darama" -> "The continent's name was changed to Darama after Daraman spread his teachings here. I don't know its old name, I'm afraid." +"daraman" -> "Daraman is acknowledged as a prophet, though it is said he was misguided. I think you should better ask somebody in the temple about such issues." +"ankrahmun" -> "Our home is blessed and protected by the power of our pharaoh." + +"pharaoh" -> "The pharaoh is a living god and his power is rising with every day." +"false", "gods" -> "Well, the temple teaches us that the false gods want to steal our Rah." +"ascension" -> "This is nothing I understand. I am but a simple man." +"Akh'rah","Uthun" -> "Thats religious stuff, and I don't know much about it. It's about the union and the separation of Akh, Rah and Uthun." +"Akh" -> "Well, that is the mortal body. It is full of needs and thus sinful." +"undead" -> "Undeath must be great. No need to eat, to sleep or to do other things like that, you know." +"undeath" -> * +"Rah" -> "The Rah is what people from other religions call the soul." +"uthun" -> "That's what we learn and remember. It is who we are because of our memories. At least that's what I understand." +"mourn" -> "Mortality is a bad thing. The dead mourn us for that. Quite nice of them. We should mourn ourselves as well, if I understand the priests correctly." +"arena" -> "The arena is a fun place to visit. You should go there to try out our quality equipment." +"palace" -> "That's where our pharaohs resides. The palace is to the east." +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legss for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." + +"sell","magic","plate","armor" -> "WOW! Do you really want to sell me a MAGIC plate armor?", Topic=3 +Topic=3,"yes" -> Type=3366, Amount=1, Price=6400,"Oh, unbelievable! I would pay %P gold for this wonderful piece of armor. Are you still interested?", Topic=4 +Topic=3 -> "Hmmm, what a pity! I have been looking for such an armor since a long time." +Topic=4,"yes",Count(Type)>=Amount -> "Finally it is mine! Here is your money. Can I be of any further help?", Delete(Type), CreateMoney +Topic=4,"yes" -> "Argl! You do not have one! Trying to tease me? Get lost or I call the guards!",Idle +Topic=4 -> "Maybe my offer is too low? Unfortunately I can not bring up more money, I am just a smith." + +} diff --git a/data/npc/mirabell.npc b/data/npc/mirabell.npc new file mode 100644 index 0000000..e71f481 --- /dev/null +++ b/data/npc/mirabell.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mirabell.npc: Datenbank für die Wirtin Mirabell + +Name = "Mirabell" +Outfit = (136,96-12-87-77) +Home = [33174,31801,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Horn of Plenty, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Come back soon, traveller." + +"bye" -> "Come back soon, traveller.", Idle +"job" -> "I am the owner of this tavern, which is known far beyond Edron." +"tavern" -> * +"frodo" -> "He's my cousin and lives in Thais." +"name" -> "My name is Mirabell." +"time" -> "It is %T right now." +"king" -> "King Tibianus III should visit our beautiful isle more often." +"tibianus" -> * +"army" -> "Sadly most of them are too disciplined to visit my tavern." +"ferumbras" -> "I heard horrible things about him." +"excalibug" -> "I heard the Knights of the True Blood are looking for it on this isle." +"thais" -> "Thais will loose influence on Edron more and more." +"tibia" -> "I think Edron is the best place in Tibia." +"carlin" -> "They should return to the Thaian realm." +"edron" -> "I think it is the best place in Tibia." +"news" -> "Oh, there are so many. Just ask other travellers like you." +"rumors" -> * + +"buy" -> "I can offer you food and drinks." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you beer, wine, lemonade, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, "Do you want to buy a mug of water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/miraia.npc b/data/npc/miraia.npc new file mode 100644 index 0000000..3bfe5f5 --- /dev/null +++ b/data/npc/miraia.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# miraia.npc: Datenbank für die Wirtin Miraia + +Name = "Miraia" +Outfit = (136,95-0-7-115) +Home = [33238,32483,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N. Welcome to the Enlightened Oasis." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will serve you in a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings. Come back soon." + +"bye" -> "Daraman's blessings. Come back soon.", Idle +"job" -> "I am the owner of this tavern, this oasis for the thirst, home of shadow and relaxation." +"tavern" -> * +"frodo" -> "He's my cousin and lives in Thais." +"name" -> "My name is Miraia." +"time" -> "Don't worry about time right now." +"caliph" -> "Sadly the caliph does not visit this humble place." +"kazzan" -> * +"ferumbras" -> "Travellers talked to me about his evilness. Thrice damned be his name." +"excalibug" -> "Some foolish adventurers seek for it in the haunted ruins of Drefia." +"thais" -> "Thais is a place of evil and corruption." +"tibia" -> "Here we are far away from the temptations of the world." +"carlin" -> "At least they shun alcohol over there." +"news" -> "Oh, just listen to the tales told by the other visitors." +"rumour" -> * +"rumor" -> * + +"buy" -> "I can offer you food and drinks." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." +"drink" -> "I can offer you lemonade, milk, and water." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you wanna buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you wanna buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you wanna buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you wanna buy %A ham for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=3, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=2, "Do you want to buy a mug of water for %P gold?", Topic=1 +"milk" -> Type=2880, Data=9, Amount=1, Price=5, "Do you want to buy a mug of camel milk for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of water for %P gold?", Topic=1 +%1,1<%1,"milk" -> Type=2880, Data=9, Amount=%1, Price=5*%1, "Do you want to buy %A mugs of camel milk for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/mortimer.npc b/data/npc/mortimer.npc new file mode 100644 index 0000000..ab8c504 --- /dev/null +++ b/data/npc/mortimer.npc @@ -0,0 +1,45 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# angus: Datenbank für den Teamassitstenten der explorers society Mortimer + +Name = "Mortimer" +Outfit = (133,57-113-95-113) +Home = [32499,31626,07] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, what can I do for you?" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "Good bye.", Idle +"farewell" -> * + +################ Später ab hier besser bd nutzen + +@"explorer.ndb" + +################ + +"mission",QuestValue(300)=12,QuestValue(320)<1 -> "With the objects you've provided our researchers will make steady progress. Still we are missing some test results from fellow explorers ...", "Please travel to our base in Port Hope and ask them to mail us their latest research reports. Then return here and ask about new missions.",SetQuestValue(320,2) + +"research","reports",QuestValue(320)=1 -> "Oh, yes! Tell our fellow explorer that the papers are in the mail already.",SetQuestValue(320,3) +"mission",QuestValue(320)=1 -> * + +##### +"mission",QuestValue(320)=4 -> "The reports from Port Hope have already arrived here and our progress is astonishing. We think it is possible to create an astral bridge between our bases. Are you interested to assist us with this?",topic=33 +##### +"no",topic=33 -> "Perhaps you are interested some other time." +"yes",topic=33 -> "Good, just take this spectral essence and use it on the strange carving in this building as well as on the corresponding tile in our base at Port Hope ...", "As soon as you have charged the portal tiles that way, report about the spectral portals.", Create(4840),SetQuestValue(320,5) + + +##### topic 34 verwendet + +} + + + diff --git a/data/npc/morun.npc b/data/npc/morun.npc new file mode 100644 index 0000000..35df1fc --- /dev/null +++ b/data/npc/morun.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# morun.npc: Datenbank für die Stadtwache Morun in Darashia + +Name = "Morun" +Outfit = (129,95-5-26-76) +Home = [33240,32393,6] +Radius = 2 + +Behaviour = { +@"guards-darama.ndb" +} diff --git a/data/npc/mugluf.npc b/data/npc/mugluf.npc new file mode 100644 index 0000000..8c6266b --- /dev/null +++ b/data/npc/mugluf.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# mugluf.npc: Datenbank für den Fleischhändler Mugluf + +Name = "Mugluf" +Outfit = (128,95-0-0-96) +Home = [33220,32416,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N, seeker of delicacies." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I will talk to you as soon as I finished this deal, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May Daraman's wisdom enlighten your soul." + +"bye" -> "May Daraman's wisdom enlighten your soul.", Idle +"name" -> "I am known as Mugluf the younger." +"job" -> "I sell meat, ham and salmon." +"time" -> "Time is nothing but another illusion." +"caliph" -> "Caliph Kazzan is our worldly leader." +"kazzan" -> * +"ferumbras" -> "Who might that be?" +"noodles" -> "That must be an important Thaian noble. Regulary Darashian delicacies are sent for him to Thais." +"excalibug" -> "O seeker of artifacts, I have no need for other weapons then a fork, a spoon, and a knife." +"thais" -> "I think we have some kind of trade agreements with them." +"tibia" -> "The world is an illusion, don't get trapped in it." +"carlin" -> "This town must be far away, we rarely even hear about it here." +"news" -> "People say there is a dark cloud gathering in the west." +"rumour" -> * +"desert" -> "It's a challenge to body and soul. Praised be Daraman to have brought us here to grow on this constant test." + +"offer" -> "I can offer you meat, ham, and salmon." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have meat, ham, and salmon." + +"meat" -> Type=3577, Amount=1, Price=7, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=10, "Do you want to buy a ham for %P gold?", Topic=1 +"salmon" -> Type=3579, Amount=1, Price=6, "Do you want to buy a salmon for %P gold?", Topic=1 + +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=7*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=10*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=6*%1, "Do you want to buy %A salmon for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/muriel.npc b/data/npc/muriel.npc new file mode 100644 index 0000000..a1ee183 --- /dev/null +++ b/data/npc/muriel.npc @@ -0,0 +1,153 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# muriel.npc: Datenbank für den Magier Muriel + +Name = "Muriel" +Outfit = (130,115-94-97-57) +Home = [32296,32263,7] +Radius = 2 + +Behaviour = { +ADDRESS,Sorcerer,"hello$",! -> "Welcome back, %N!" +ADDRESS,Sorcerer,"hi$",! -> * +ADDRESS,"hello$",! -> "Greetings, %N! Looking for wisdom and power, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the second sorcerer. I am selling spellbooks and spells." +"name" -> "You may call me Muriel." +"time" -> "Time is unimportant." +"king" -> "The king is a patron of the arcane arts." +"tibianus" -> * +"quentin" -> "He has some minor magic powers." +"lynda" -> "Pretty and compentent." +"harkath" -> "He's not as dumb as the average fighter but a warrior nonetheless." +"army" -> "We supply the army with some sorcerer recruits now and then." +"general" -> * +"ferumbras" -> "I wonder how he actually got this awesome powers." +"sam" -> "A simple smith." +"xodet" -> "He has our permission to sell mana fluids." +"frodo" -> "A bar is no place that suits a scholar like me." +"elane" -> "She is quite proud of her puny magic tricks." +"muriel" -> "I don't like jokes about my name!" +"gregor" -> "Knights! Childs with swords. Not worth of any attention." +"marvik" -> "Marvik and his Sorcerers lack spells with real power." +"bozo" -> "He's not a jester but a poor joke himself." +"baxter" -> "I don't know him." +"oswald" -> "Only his boss keeps him from being burned to ashes." +"sherry" -> "Simple farmers." +"donald" -> * +"mcronald" -> * +"lugri" -> "He is rumoured to posses some secrets our guild might find ... interesting." +"lungelen" -> "She keeps the whole wisdom of our ancestors and leads our guild." +"excalibug" -> "The enchantements on this weapon must be awesome." +"news" -> "Our guild is working on a new spell, but I won't give away any details yet." +"flaming","pit" -> "These pits, you refer to, might be the legendary 'Pits of Inferno', also known as the 'Nightmare Pits'." +"pits","inferno" -> "They are rumoured to be hidden somewhere in the Plains of Havoc, far to the east." +"nightmare","pit"-> * + +"wisdom" -> "The wisdom of spellcasting is the source of power." +"ancestor" -> "There were many generations of sorcerers in the past. Today a lot of people want to join us." +"sorcerer" -> "A sorcerer spends his lifetime studying spells to gain power." +"power" -> "Of course, power is the most important thing in the universe." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and Sorcerers." +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you can find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 +"rune" -> "Each spell, that starts with 'Ad', needs a rune. You have to hold a blank rune in one of your hands when you cast it. You can buy runes at the magic shop." +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back when you have enough money." +Topic=4 -> "Hmm, maybe next time." +} diff --git a/data/npc/muzir.npc b/data/npc/muzir.npc new file mode 100644 index 0000000..41a5ac0 --- /dev/null +++ b/data/npc/muzir.npc @@ -0,0 +1,29 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# muzir.npc: Datenbank für den Wesir Muzir + +Name = "Muzir" +Outfit = (128,95-4-11-76) +Home = [33222,32390,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Daraman's blessings." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy. Please wait for your turn.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"job" -> "I am honoured to be the grandwezir of the caliph." +"name" -> "I am Muzir." ### Ibn ??? ### +"time" -> "It is exactly %T." +"caliph" -> "I am caretaker for the fortune of our beloved and wise caliph." +"kazzan" -> * +"daraman" -> "I take it upon me to involve myself with worldly issues for the prosperity of our community. I hope the taint of wealth does not harm my soul too much." +"wezir" -> "I am responsible for the wealth of our beloved and wise caliph. I can also change money for you." +"wealth" -> * + +@"gen-bank.ndb" +} diff --git a/data/npc/myra.npc b/data/npc/myra.npc new file mode 100644 index 0000000..d184173 --- /dev/null +++ b/data/npc/myra.npc @@ -0,0 +1,140 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# myra.npc: Datenbank für die Magierlehrerin Myra + +Name = "Myra" +Outfit = (140,115-0-19-95) +Home = [32580,32751,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Sorry, not now.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am a sorcerer. I was sent here by the academy of Edron to function as an adviser in magical matters and as a teacher for sorcerers in need of training." +"name" -> "Myra is my name." +"time" -> "It is %T right now." +"king" -> "We are here on the behalf of the king and try our best to make this colony prosper." +"venore" -> "I find the Venoran activity here disturbing, but, after all, that's not my business." +"thais" -> "Thais lacks the lovely peace of Edron, but as the capital of the Thaian kingdom it offers more chances to study or entertain yourself than this fledgling city." +"carlin" -> "The druids of Carlin could do a lot with all the freedom they have, but they waste their resources in some strange cult and lack any scientific approach to magic." +"edron" -> "I loved my time at the academy. I had my differences with some superiors though, and when it came to select somebody to come here, my name was mentioned once too often I think." +"jungle" -> "I am working on a spell aiming specifically on destroying plant life. I am sure it would be of enormous help and would earn me a positon in the academy once more." + +"tibia" -> "I have already seen more of the world as I had ever planned." + +"kazordoon" -> "I would have even preferred an appointment to the dark halls of Kazordoon than to this colony." +"dwarves" -> "Dwarves are good miners, I can't say much more about them." +"dwarfs" -> * +"ab'dendriel" -> "Elves would probably be more suitable to this environment." +"elves" -> * +"elfs" -> * +"darama" -> "I think all this talk about the conquest of a new continent is simply exaggerated." +"darashia" -> "Living in the desert must be even worse than living here." +"ankrahmun" -> "Although I'd love to study the undeath more closely, I'd not want to study it first hand." +"ferumbras" -> "He wastes all his power to spread terror and destruction. Doesn't this become boring after a while?" +"excalibug" -> "The magic used to create that weapon would be more interesting than the weapon itself." +"apes" -> "They are annoying but easily driven away." +"lizard" -> "The lizards are somewhat mysterious, but who would care to travel through the whole cursed jungle to learn the boring secrets of some fly-eaters?" +"dworcs" -> "Sooner or later we will have to face this threat in the south." + +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and druids." +"rune" -> "Each spell, that starts with 'Ad', needs a rune. You have to hold a blank rune in one of your hands when you cast it. You can buy runes at the magic shop." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." +} diff --git a/data/npc/nahbob.npc b/data/npc/nahbob.npc new file mode 100644 index 0000000..8490308 --- /dev/null +++ b/data/npc/nahbob.npc @@ -0,0 +1,172 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nahbob.npc: Datenbank für den Maridhändler Nah'bob (Waffen und Rüstungen, Marid) + +Name = "Nah'bob" +Outfit = (80,0-0-0-0) +Home = [33104,32520,2] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> " Another customer! I've only just sat down! What is it, %N?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=2,! -> "Whoa, %N! Easy! Old Bob is busy right now!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,! -> NOP +VANISH -> "Bye then." + +"bye" -> "Bye now. Visit old Bob again one day!", Idle +"farewell" -> * +"name" -> "Well, my name is Nah'bob, but I don't like it. Just call me Bob." +"Bob" -> "Yep, that's me." +"nah'bob" -> "I don't like that name. Call me Bob." +"job",male -> "I'm a trader, mate. You know... selling stuff, buying stuff... that kind of thing. Quite a hassle, actually. ...", + "If you have the permission to trade with us, check out my latest offers." +"job",female -> "I'm a trader, lady. You know... selling stuff, buying stuff... that kind of thing. Quite a hassle, actually. ...", + "If you have the permission to trade with us, check out my latest offers." +"trade",male -> "You want to buy something? Well, be my guest, Mister. Just ask me for my offers!" +"trade",female -> "You want to buy something? Well, be my guest, Lady. Just ask me for my offers!" +"permission" -> "I am not allowed to trade with you unless Gabel gave you the permission to trade with us." + +"gabel" -> "He's the boss around here. You know - the big djinn. He's all right." +"king" -> "King? We do not have kings. Not anymore." +"djinn" -> "Yes, I am a djinn. How long did it take you to notice?" +"efreet" -> "The Efreet are a bunch of freaked out loonies, believe me. Nothing but 'war!' and 'domination!' and 'world rule!' all day long. Those people shoud chill out a bit." +"marid" -> "I guess I am one. I don't know why because I'm not really into all that Daraman stuff about ascetism, you know. ...", + "But then, I don't like the Efreets' gibberish about war and world domination, either. ...", + "In the end I chose the side which seemed more tolerant towards attitudes that are a bit... different. And that was clearly the Marid. ...", + "Besides, Bo'ques chose to side with Gabel. And I would have hated never to eat his cooking again. Did you ever try his Chat d'Aramignon?" +"malor" -> "Now THAT djnn really means stress. Too bad that doofus of an orc king freed him from his prison in the lamp. Gabel should have gotten rid of Malor for good when had the chance to do so." +"mal'ouquah" -> "Mal'ouquah the Efreet's fortress. I have never seen it, and I don't feel any particular desire to do so." +"ashta'daramai" -> "Ashta'daramai is the name of this place - 'Daraman's gift'. It's actually a bit misleading, because it was not Daraman who built it. The name refer's to Daraman's teachings, you know." +"human" -> "I have nothing against humans. ...", + "Sure, there are always those who want to steal from you. ...", + "Or blackmail you. ...", + "Or torture you. ...", + "Or just kill you for no reason. ...", + "But all in all I think humans are amusing little people." +"zathroth" -> "They say Zathroth created us because he wanted us to be mindless, greedy killing machines. Well, I'm not so sure about some of the Efreet, but as for me I think he's done a lousy job." +"tibia" -> "It's a mad, mad world." +"daraman" -> "Ah yes. Daraman. The old prophet. Well - he was all right, I guess. But I am not sure I agree with him on all matters. I mean - that thing about asceticism... I think he was a bit extreme there." +"darashia" -> "They say Darashia is full of relaxed people. To me that sounds like a good place to be." +"scarab" -> "Yuk! I wouldn't eat that!" +"edron" -> "You are talking about some human place, aren't you?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I know Ankrahmun - I've been there before. Lots of pyramids and stuff. Wicked." +"pharaoh" -> "The pharaoh? I have heard about that guy. That man is as mad as a march hare." +"palace" -> "The palace in Ankrahmun used to be a place of orgies and of all kinds of debauchery. Aah - those were the days...!" +"ascension" -> "Yeah right. I don't know what it is about you humans. I have rarely met a human who did not have his head filled with some crazy religious idea." +"kha'zeel" -> "I like the Kha'zeel mountains. It is true they are boring as hell, but personally I don't mind boredom. I hate excitement. Whenever somebody said 'Oh-that's-gonna-be-so-exciting' some really, really bad thing happened." +"kha'labal" -> "Did you know that desert used to be fertile land? You should have seen it. It was all lush and verdant - a veritable paradise. ...", + "There were flowers and birds everywhere, and more delicious food anybody could ever eat. ...", + "But then that blockhead Baa'leal decided it was a good idea to burn it all down. Damn those stupid Efreet!" +"melchior" -> "Of course I remember old Melchior. He used to come up here regularly. Is he dead?", Topic=1 +Topic=1,"yes" -> "Oh. Well, I suppose that is bad. For him, anyway." +Topic=1,"no" -> "So the old skinflint is still alive? Amazing! I could have sworn the Efreet knocked the stuffing out of him. No human can fool a djinn for long, you know." +"alesar" -> "I know Alesar. Ran off to the Efreet, didn't he? Weird story. Always used to be such fanatic followers of Daraman, he and his brother. ...", + "I guess that happens when you get all freaky about some silly idea. I'm sure that wouldn't have happened if he had taken a more relaxed approach to things. ...", + "I remember how I always told him to chill out. But he was stubborn as a headstrong donkey. Just like his brother." +"brother" -> "Haroun is Alesar's brother. Don't talk to him about it, though. Haroun may be a zealous fool, but he's all right. He has suffered enough." +"fa'hradin" -> "I think Fa'hradin has stopped taking things too seriously long time ago. You just got to love the old cynic." +"bo'ques" -> "Honestly, if I did not have any other reason for hanging out here Bo'ques's cooking would make me stay." +"djem" -> "The little girl djema is bored out of her mind here so she often comes round. We've always had a barrel of laughs together, she and me - playing chess, telling stories, playing tricks on Haroun. ...", + "I really like her. I will miss her lots when she's gone. Hey! Don't tell her I have said that, ok?" +"haroun" -> "Ah yes - Haroun. My esteemed fellow trader. Stiff like a butler and boring like a tax collector. He is a devout follower of Daraman, but I think he seriously misunderstood his teachings. ...", + "I don't think it was the old prophet's intention to rid this world of laughter and of smiles." +"baa'leal" -> "Baa'leal? Of course I remember old bulldog face. I wasn't surprised at all when I heard that he sided with Malor. Wasn't a great loss anyway. ...", + "He may be one mean djinn, but he is thicker than a senile ox. To think Malor made him his lieutenant !" + +"wares" -> "My job around here is to buy and sell weapons, armors, helmets, legs, and shields." +"offer" -> * +"goods" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"weapon" -> "I'm selling and buying spike swords, war hammers and obsidian lances. Furthermore I would buy fire swords, ice rapiers, dragon lances, fire axes and broadswords from you, if you have any." +"shield" -> "I'm just selling beholder shields. But I'm buying guardian shields, dragon shields, beholder shields, crown shields and phoenix shields." +"armor" -> "I'm selling and buying noble armors. Furthermore I'm buying crown armors and blue robes." +"helmet" -> "At this time I'm not selling any helmets. I'm only buying crown helmets, crusader helmets and royal helmets." +"trousers" -> "At this time I'm only buying crown legs. Oh, and I'm also looking for boots of haste!" +"legs" -> * + +"spike","sword" -> Type=3271, Amount=1, Price=8000, "Do you want to buy a spike sword for %P gold?", Topic=10 +"war","hammer" -> Type=3279, Amount=1, Price=10000, "Do you want to buy a war hammer for %P gold?", Topic=10 +"obsidian","lance" -> Type=3313, Amount=1, Price=3000, "Do you want to buy an obsidian lance for %P gold?", Topic=10 +"beholder","shield" -> Type=3418, Amount=1, Price=7000, "Do you want to buy a beholder shield for %P gold?", Topic=10 +"noble","armor" -> Type=3380, Amount=1, Price=8000, "Do you want to buy a noble armor for %P gold?", Topic=10 + +%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=8000*%1, "Do you want to buy %A spike swords for %P gold?", Topic=10 +%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=10000*%1, "Do you want to buy %A war hammers for %P gold?", Topic=10 +%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=3000*%1, "Do you want to buy %A obsidian lances for %P gold?", Topic=10 +%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=7000*%1, "Do you want to buy %A beholder shields for %P gold?", Topic=10 +%1,1<%1,"noble","armor" -> Type=3380, Amount=%1, Price=8000*%1, "Do you want to buy %A noble armors for %P gold?", Topic=10 + +"sell","spike","sword" -> Type=3271, Amount=1, Price=1000, "Do you want to sell a spike sword for %P gold?", Topic=11 +"sell","fire","sword" -> Type=3280, Amount=1, Price=4000, "Do you want to sell a fire sword for %P gold?", Topic=11 +"sell","war","hammer" -> Type=3279, Amount=1, Price=1200, "Do you want to sell a war hammer for %P gold?", Topic=11 +"sell","ice","rapier" -> Type=3284, Amount=1, Price=1000, "Do you want to sell an ice rapier for %P gold?", Topic=11 +"sell","broad","sword" -> Type=3301, Amount=1, Price=500, "Do you want to sell a broad sword for %P gold?", Topic=11 +"sell","dragon","lance" -> Type=3302, Amount=1, Price=9000, "Do you want to sell a dragon lance for %P gold?", Topic=11 +"sell","obsidian","lance" -> Type=3313, Amount=1, Price=500, "Do you want to sell an obsidian lance for %P gold?", Topic=11 +"sell","fire","axe" -> Type=3320, Amount=1, Price=8000, "Do you want to sell a fire axe for %P gold?", Topic=11 + +"sell","guardian","shield" -> Type=3415, Amount=1, Price=2000, "Do you want to sell a guardian shield for %P gold?", Topic=11 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=4000, "Do you want to sell a dragon shield for %P gold?", Topic=11 +"sell","beholder","shield" -> Type=3418, Amount=1, Price=1200, "Do you want to sell a beholder shield for %P gold?", Topic=11 +"sell","crown","shield" -> Type=3419, Amount=1, Price=8000, "Do you want to sell a crown shield for %P gold?", Topic=11 +"sell","phoenix","shield" -> Type=3439, Amount=1, Price=16000, "Do you want to sell a phoenix shield for %P gold?", Topic=11 + +"sell","noble","armor" -> Type=3380, Amount=1, Price=900, "Do you want to sell a noble armor for %P gold?", Topic=11 +"sell","crown","armor" -> Type=3381, Amount=1, Price=12000, "Do you want to sell a crown armor for %P gold?", Topic=11 +"sell","crown","legs" -> Type=3382, Amount=1, Price=12000, "Do you want to sell a pair of crown legs for %P gold?", Topic=11 +"sell","crown","helmet" -> Type=3385, Amount=1, Price=2500, "Do you want to sell a crown helmet for %P gold?", Topic=11 +"sell","crusader","helmet" -> Type=3391, Amount=1, Price=6000, "Do you want to sell a crusader helmet for %P gold?", Topic=11 +"sell","royal","helmet" -> Type=3392, Amount=1, Price=30000, "Do you want to sell a royal helmet for %P gold?", Topic=11 +"sell","blue","robe" -> Type=3567, Amount=1, Price=10000, "Do you want to sell a blue robe for %P gold?", Topic=11 +"sell","boots","of","haste" -> Type=3079, Amount=1, Price=30000, "Do you want to sell a boots of haste for %P gold?", Topic=11 + +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=1000*%1, "Do you want to sell %A spike swords for %P gold?", Topic=11 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=4000*%1, "Do you want to sell %A fire swords for %P gold?", Topic=11 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=1200*%1, "Do you want to sell %A war hammers for %P gold?", Topic=11 +"sell",%1,1<%1,"ice","rapier" -> Type=3284, Amount=%1, Price=1000*%1, "Do you want to sell %A ice rapiers for %P gold?", Topic=11 +"sell",%1,1<%1,"broad","sword" -> Type=3301, Amount=%1, Price=500*%1, "Do you want to sell %A broad swords for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","lance" -> Type=3302, Amount=%1, Price=9000*%1, "Do you want to sell %A dragon lances for %P gold?", Topic=11 +"sell",%1,1<%1,"obsidian","lance" -> Type=3313, Amount=%1, Price=500*%1, "Do you want to sell %A obsidian lances for %P gold?", Topic=11 +"sell",%1,1<%1,"fire","axe" -> Type=3320, Amount=%1, Price=8000*%1, "Do you want to sell %A fire axes for %P gold?", Topic=11 + +"sell",%1,1<%1,"guardian","shield" -> Type=3415, Amount=%1, Price=2000*%1, "Do you want to sell %A guardian shields for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=4000*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=11 +"sell",%1,1<%1,"beholder","shield" -> Type=3418, Amount=%1, Price=1200*%1, "Do you want to sell %A beholder shields for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","shield" -> Type=3419, Amount=%1, Price=8000*%1, "Do you want to sell %A crown shields for %P gold?", Topic=11 +"sell",%1,1<%1,"phoenix","shield" -> Type=3439, Amount=%1, Price=16000*%1, "Do you want to sell %A phoenix shields for %P gold?", Topic=11 + +"sell",%1,1<%1,"noble","armor" -> Type=3380, Amount=%1, Price=900*%1, "Do you want to sell %A noble armors for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","armor" -> Type=3381, Amount=%1, Price=12000*%1, "Do you want to sell %A crown armors for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","legs" -> Type=3382, Amount=%1, Price=12000*%1, "Do you want to sell %A pairs of crown legs for %P gold?", Topic=11 +"sell",%1,1<%1,"crown","helmet" -> Type=3385, Amount=%1, Price=2500*%1, "Do you want to sell %A crown helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"crusader","helmet" -> Type=3391, Amount=%1, Price=6000*%1, "Do you want to sell %A crusader helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"royal","helmet" -> Type=3392, Amount=%1, Price=30000*%1, "Do you want to sell %A royal helmets for %P gold?", Topic=11 +"sell",%1,1<%1,"blue","robe" -> Type=3567, Amount=%1, Price=10000*%1, "Do you want to sell %A blue robes for %P gold?", Topic=11 +"sell",%1,1<%1,"boots","of","haste" -> Type=3079, Amount=%1, Price=30000*%1, "Do you want to sell %A boots of haste for %P gold?", Topic=11 + +Topic=10,QuestValue(283)<3,! -> "I'm sorry, pal. But you need Gabel's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Ok. Here you are!", DeleteMoney, Create(Type) +Topic=10,"yes" -> "Well, come back if you have enough gold." +Topic=10 -> "Well, obviously not." + +Topic=11,QuestValue(283)<3,! -> "I'm sorry, pal. You don't have Gabel's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Ok. Here is your gold.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one!" +Topic=11,"yes",Amount>1 -> "You do not have that many!" +Topic=11 -> "Well, obviously not." +} diff --git a/data/npc/nelliem.npc b/data/npc/nelliem.npc new file mode 100644 index 0000000..6d09677 --- /dev/null +++ b/data/npc/nelliem.npc @@ -0,0 +1,64 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Nelliem.npc: Datenbank für den Gartenbedarfhändler Nelliem + +Name = "Nelliem" +Outfit = (160,115-100-105-76) +Home = [32883,32086,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N, traveller from afar..." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hold on, %N, I am busy. Just stand in the line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Go...! Learn the secret to green thumbs and may Crunor be good to you..." + +"bye" -> "Go...! Learn the secret to green thumbs and may Crunor be good to you...", Idle +"farewell" -> * +"job" -> "To keep my thumbs green and to sell our garden equipment, as you can see on that shelves." +"crunor" -> "May he bless all plants." +"name" -> "I am Nelliem." +"time" -> "It's a good time to sow some seeds." + +"equipment" -> "I sell shovels, picks, scythes, machetes, ropes, pitchforks, rakes, hoes, brooms, fishing rods, sixpacks of worms and brandnew crowbars from Kazordoon." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"shovel" -> Type=3457, Amount=1, Price=20, "Do you want to buy a shovel for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=25, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you want to buy a dwarfensteel crowbar for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"pitchfork" -> Type=3451, Amount=1, Price=25, "Do you want to buy a pitchfork for %P gold?", Topic=1 +"rake" -> Type=3452, Amount=1, Price=20, "Do you want to buy a rake for %P gold?", Topic=1 +"hoe" -> Type=3455, Amount=1, Price=15, "Do you want to buy a hoe for %P gold?", Topic=1 +"broom" -> Type=3454, Amount=1, Price=12, "Do you want to buy a broom for %P gold?", Topic=1 + +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=20*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=25*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you want to buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"pitchfork" -> Type=3451, Amount=%1, Price=25*%1, "Do you want to buy %A pitchforks for %P gold?", Topic=1 +%1,1<%1,"rake" -> Type=3452, Amount=%1, Price=20*%1, "Do you want to buy %A rakes for %P gold?", Topic=1 +%1,1<%1,"hoe" -> Type=3455, Amount=%1, Price=15*%1, "Do you want to buy %A hoes for %P gold?", Topic=1 +%1,1<%1,"broom" -> Type=3454, Amount=%1, Price=12*%1, "Do you want to buy %A brooms for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No money, no deal!" +Topic=1 -> "Then not." +} diff --git a/data/npc/nemal.npc b/data/npc/nemal.npc new file mode 100644 index 0000000..b3530c7 --- /dev/null +++ b/data/npc/nemal.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nemal.npc: Datenbank für den Blinden Nemal (Desert) + +Name = "Nemal" +Outfit = (129,57-97-45-115) +Home = [32615,32108,10] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello. How are you? Maybe you can help me: Do my shoes have the same colour?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Is there someone else? Step closer!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Where are you? Are you gone?" + +"bye" -> "Farewell!", Idle +"farewell" -> "May the light be with you!", Idle +"name" -> "My name is Nemal." +"job" -> "I have no job. I'm a wanderer." +"thais" -> "Yes, I used to live there a while ago. Nice town." +"carlin" -> "Aaaahh, Carlin, yes. I know this town. Strange graveyard there, isn't it?" +"king" -> "King...king...yes, there is one. I still can remember the time when we had an other king. What was his name again?" +"weapon" -> "I always have weapons with me. You never know what's behind the next corner." +"help" -> "Hmm.. help? How can I help you?" +"time" -> "I had a watch. It was nice, but I can't see the hands anymore." +"sword" -> "I have a sword. It's very sharp. But I don't give it away, you never know." +"desert" -> "Desert? Where is one?" +"excalibug" -> "I heard the name, but I don't trust rumours." +"fight" -> "Better know how to fight!" +"guild" -> "Hmmm.. I wouldn't ever join a guild, but i know of the existance of some." +"god" -> "I don't believe in gods, but a lot of people do. I never saw a god, why should I trust in someone I never met?" +"way" -> "There are many ways. I don't know all of them." +"door" -> "Closed doors need keys. What a pity keys are not lying around like rubbish!" +"library" -> "I can't read or write. What use would I have of a library?" +"secret" -> "Secrets should remain secret. No need of making them public." +"treasure" -> "You can find some treasures here - some are bigger, some are smaller, some are of true value, some are of materialistic value." +"book" -> "I can't read. I've never learned it." +"gharonk" -> "My father knew this language." +"offer" -> "I don't sell things. If you really need something, better walk straight to one of the towns or ask another adventurer." +"blind" -> "Yes, I seem to be blind. But I am not sure - maybe the dungeons are too dark!" + +"potion","regain","vision" -> "I heard of a potion of regained vision ... but I can't remember how to make it! Maybe you can help me. Do you know something about it?", Topic=2 +Topic=2,"yes" -> "So, did you bring the ingredients with you, stranger?", Topic=3 +Topic=2 -> "Oh. Maybe someone else could do it, then." +Topic=3,"yes",Count(3601)>0,Count(3603)>0,Count(3604)>0,Count(3658)>0,Count(3590)>0 -> "You seem to have them with you. Can you tell me, how many minutes I have to cook them?", Amount=1, Delete(3601), Delete(3603), Delete(3604), Delete(3658), Delete(3590), Topic=4 +Topic=3,"yes" -> "It doesn't seem to me as if you have the correct ingredients with you, stranger!" +Topic=3 -> "Maybe you can find them!" +Topic=4,"31" -> "Ah. It seems to work. But what are the words I have to speak?", Topic=5 +Topic=4 -> "Oh no, I don't think this is right." +Topic=5,"nalus","murtu" -> "Thank you! NALUS MURTUUU! ... I can see again! To show you, how grateful I am, I'll give you a key. Be wise when using it. I can't tell you, where it matches, but ... take good care, it is useless without mental powers!", Data=4037, Create(2969) +Topic=5 -> "Oh no, I don't think these are the right words." +} diff --git a/data/npc/nezil.npc b/data/npc/nezil.npc new file mode 100644 index 0000000..711455a --- /dev/null +++ b/data/npc/nezil.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nezil.npc: Datenbank für den Händler Nezil + +Name = "Nezil" +Outfit = (160,115-78-116-57) +Home = [32661,31912,9] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$","nezil",! -> "Hiho %N, Nezil at your service." +ADDRESS,"hi$","nezil",! -> * +ADDRESS,"hiho$","nezil",! -> * +ADDRESS,"hello$",! -> "Uhm, me or my sis', %N?", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","nezil",! -> "Hey %N, I am busy. Stand in line.", Queue +BUSY,"hi$","nezil",! -> * +BUSY,"hiho$","nezil",! -> * +BUSY,"hello$",! -> "Uhm, me or my sis', %N?", Idle +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"hello$","bezil" -> "Good bye.", Idle +"hi$","bezil" -> * +"hiho$","bezil" -> * +"bye" -> * +"farewell" -> * +"job" -> "We sell equipment of all kinds. Is there anything you need?" +"equipment" -> "We sell shovels, picks, scythes, bags, ropes, backpacks, cups, scrolls, documents, parchments, and watches. We also sell lightsources." +"goods" -> * +"light" -> "We sell torches, candlesticks, candelabra, and oil." +"name" -> "I am Nezil Whetstone, son of Fire, of the Savage Axes. I and my sis' Bezil are selling stuff, ye' know?" +"bezil" -> "She's my sis'." +"time" -> "I think it's about %T. If you'd bought a watch you'd know for sure." +"food" -> "Sorry, visit the Jolly Axeman Tavern for that." + +"goods" -> "Let me see ... we have shovels, picks, scythes, bags, ropes, backpacks, scrolls, documents, parchments, watches, fishing rods, sixpacks of worms and some lightsources." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"bag" -> Type=2862, Amount=1, Price=4, "Do you wanna buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you wanna buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you wanna buy a shovel for %P gold?", Topic=1 +"backpack" -> Type=2870, Amount=1, Price=10, "Do you wanna buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=40, "Do you wanna buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you wanna buy a pick for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you wanna buy one of my high quality watches for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you wanna buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you wanna buy a fishing rod for %P gold?", Topic=1 +"crowbar" -> Type=3304, Amount=1, Price=260, "Do you wanna buy a dwarfensteel crowbar for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you wanna buy a present for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you wanna buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you wanna buy a bottle for %P gold?", Topic=1 +"water","hose" -> Type=2901, Amount=1, Price=10, Data=1, "Do you wanna buy a water hose for %P gold?", Topic=1 +"oil" -> Type=2874, Amount=1, Price=20, Data=7, "Do you wanna buy oil for %P gold?", Topic=2 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2862, Amount=%1, Price=4*%1, "Do you wanna buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you wanna buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you wanna buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2870, Amount=%1, Price=10*%1, "Do you wanna buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=40*%1, "Do you wanna buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you wanna buy %A picks for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you wanna buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you wanna buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you wanna buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"crowbar" -> Type=3304, Amount=%1, Price=260*%1, "Do you wanna buy %A dwarfensteel crowbars for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you wanna buy %A presents for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you wanna buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you wanna buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"water","hose" -> Type=2901, Amount=%1, Price=10*%1, Data=1, "Do you wanna buy %A water hoses for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Amount=%1, Price=20*%1, Data=7, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here, catch it!", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Nice joke, pauper!" +Topic=1 -> "Then not." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Nice joke, pauper!" +Topic=2 -> "Then not." + +Topic=3,"yes",Count(Type)>=Amount -> "Ok. Here's your money.", Delete(Type), CreateMoney +Topic=3,"yes" -> "Sorry, you are not having one." +Topic=3 -> "Maybe next time." + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." +} diff --git a/data/npc/noodles.npc b/data/npc/noodles.npc new file mode 100644 index 0000000..87df90d --- /dev/null +++ b/data/npc/noodles.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# noodles.npc: Datenbank für den Pudel Noodles + +Name = "Noodles" +Outfit = (32,0-0-0-0) +Home = [32315,32178,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> Type=3577, Amount=1," Woof! " +ADDRESS,"hi$",! -> * +ADDRESS,! -> "Grrrr!", Idle +BUSY,"hi$",! -> "Grrr! Woof!" +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Woof?? " + +"bye" -> "Woof! ", Idle +"farewell" -> * +"how","are","you"-> "Wooooof! " +"king" -> * +"tibianus" -> * +"cat$" -> "GRRRRRRR! WOOOOOOF! WOOOOOF! WOOOOOF!" +"queen" -> * +"eloise" -> * + + +"pork",Count(Type)>=Amount -> "Woof! Woof! ", Delete(Type) + +"sniff","banana", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","banana", QuestValue(233)=7, QuestValue(251)=1 -> Type=3104, Amount=1," ",Topic=2 +"sniff","fur", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","fur", QuestValue(233)=7, QuestValue(251)=1 -> Type=3105, Amount=1," ",Topic=2 +"sniff","cheese", QuestValue(233)<>7 -> "Woof!",Idle +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=1 -> Type=3120, Amount=1," ",Topic=3 + + +"sniff","banana", QuestValue(233)=7, QuestValue(251)=2 -> Type=3104, Amount=1," ",Topic=2 +"sniff","fur", QuestValue(233)=7, QuestValue(251)=2 -> Type=3105, Amount=1," ",Topic=3 +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=2 -> Type=3120, Amount=1," ",Topic=2 + +"sniff","banana", QuestValue(233)=7, QuestValue(251)=3 -> Type=3104, Amount=1," ",Topic=3 +"sniff","fur", QuestValue(233)=7, QuestValue(251)=3 -> Type=3105, Amount=1," ",Topic=2 +"sniff","cheese", QuestValue(233)=7, QuestValue(251)=3 -> Type=3120, Amount=1," ",Topic=2 + + + +topic=2,"like","that",Count(Type)>=Amount -> "Woof!" +topic=3,"like","that",Count(Type)>=Amount -> "Meeep! Grrrrr! ",SetQuestValue(233,8),Idle + + +"ferumbras" -> "Meeep! Meeep!",Idle +"th" -> "" +"ar" -> "Woof!" +"bo" -> "" +"an" -> "Grrrr!" +"go" -> "Woof! Woof!" +} diff --git a/data/npc/norbert.npc b/data/npc/norbert.npc new file mode 100644 index 0000000..0796de9 --- /dev/null +++ b/data/npc/norbert.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norbert.npc: Datenbank für den Schneider Norbert + +Name = "Norbert" +Outfit = (128,6-79-93-14) +Home = [32953,32103,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",Level>40,! -> "Wow! The great %N visiting our shop! LOOK PEOPLE, LOOK HERE!." +ADDRESS,"hi$",Level>40,! -> * +ADDRESS,"hello$",! -> "Welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me another minute with this customer here, dear %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"name" -> "I am Norbert." +"job" -> "I am a salesperson here, but one day I might become a tailor, or a supermodel perhaps!" +"time" -> "Now it's %T. Did you notice this is a xelor watch I am wearing?" +"xelor" -> "Xelor, the dwarf of the chromancers guild, makes the most stylish watches in all the land." +"king" -> "Even the king of Thais, blessed be his name, can't buy a better wardrobe then ours." +"tibianus" -> * +"army" -> "I don't think they dress that well." +"ferumbras" -> "Those evil mages dress so ugly." +"excalibug" -> "I fear such a weapon will ruin a silk shirt with one blow." +"thais" -> "Thaian wear is not that stylish anymore." +"tibia" -> "Our tailors are influenced by styles of the whole known world." +"carlin" -> "Women could do better then to wear armor. Women in leather scare my in particular." +"hugo" -> "He's our boss, a great tailor and designer." +"chief" -> * +"news" -> "I heared the colour of the next season will be orange." +"rumour" -> * +"rumor" -> * + +"offer" -> "I sell very stylish clothes indeed." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"clothes" -> "I have wonderful jackets, coats, lovely doublets, even warlike leather armor, and impressive studded armor." + +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=12, "Oh, do you want to buy one of my wonderful jackets for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"studded","armor" -> Type=3378, Amount=1, Price=90, "Do you want to buy a studded armor for %P gold?", Topic=1 + +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=12*%1, "Oh, do you want to buy %A of my wonderful jackets for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A doublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"studded","armor" -> Type=3378, Amount=%1, Price=90*%1, "Do you want to buy %A studded armors for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "And here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough money." +Topic=1 -> "What a pity, perhaps next time." +} diff --git a/data/npc/norf.npc b/data/npc/norf.npc new file mode 100644 index 0000000..8eea6ee --- /dev/null +++ b/data/npc/norf.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norf.npc: Datenbank für den Mönch Norf + +Name = "Norf" +Outfit = (57,0-0-0-0) +Home = [32346,32363,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, Pilgrim." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I am here to provide one of the five blessings." +"name" -> "My name is Norf." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> "Here in the whiteflower temple you may receive the blessing of spiritual shielding. But we must ask of you to sacrifice 10.000 gold. Are you still interested?",Price=10000, Topic=5 +"shielding" -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." + +"fire" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." +"suns" -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(104) > 0,! -> "You already possess this blessing." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the shielding of your spirit, pilgrim.", DeleteMoney, EffectOpp(13),SetQuestValue(104,1), Bless(1) +Topic=5,! -> "Ok. Suits me." + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." + +} diff --git a/data/npc/norma.npc b/data/npc/norma.npc new file mode 100644 index 0000000..bf1f9cb --- /dev/null +++ b/data/npc/norma.npc @@ -0,0 +1,169 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# norma.npc: Datenbank für die Händlerin Norma (Newbie) + +Name = "Norma" +Outfit = (136,78-76-72-96) +Home = [32098,32180,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",premium,! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",premium,! -> * +ADDRESS,"hello$",! -> "I'm sorry %N, but I only serve premium account customers.", Idle +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",premium,! -> "Not now, not now, sorry %N. Please wait a moment.", Queue +BUSY,"hi$",premium,! -> * +BUSY,"hello$",! -> "I'm sorry %N, but I only serve premium account customers." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant. What can I do for you?" +"name" -> "My name is Norma. Do you want to buy something?" +"time" -> "It is about %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is safe from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only some dared to take the risk, and a risk it was!" +"dallheim" -> "Some call him a hero." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich woman!" +"thais" -> "Thais is a crowded town." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a dublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "Sorry, I fear Al Dee owns the last ones on this isle." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/nydala.npc b/data/npc/nydala.npc new file mode 100644 index 0000000..0650b6f --- /dev/null +++ b/data/npc/nydala.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# nydala.npc: Möbelverkäuferin Nydala in Carlin + +Name = "Nydala" +Outfit = (136,76-86-86-96) +Home = [32327,31827,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Carlin Furniture Store, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Nydala. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/obi.npc b/data/npc/obi.npc new file mode 100644 index 0000000..378b33f --- /dev/null +++ b/data/npc/obi.npc @@ -0,0 +1,172 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# obi.npc: Datenbank für den Händler Obi + +Name = "Obi" +Outfit = (128,39-63-96-38) +Home = [32109,32204,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello, hello, %N! Please come in, look, and buy!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please stand in line %N. I'll be with you in a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"how","are","you" -> "I am fine, I am fine. I'm so glad to have you here as my customer." +"sell" -> "I sell much, much indeed. Just read the blackboards for my awesome wares or just ask me." +"job" -> "I am a merchant, just a humble merchant. What can I do for you?" +"name" -> "My name is Obi, just Obi, the honest merchant. Do you want to buy something?" +"time" -> "It is about %T. Yes, %T. I am so sorry, I have no watches to sell. Do you want to buy something else?" +"help" -> "I sell stuff to prices that low, that all other merchants would mock at my stupidity." +"monster" -> "If you want to challenge the monsters, you need some weapons and armor I sell. You need them definitely!" +"dungeon" -> "If you want to explore the dungeons, you have to equip yourself with the vital stuff I am selling. It's vital in the deepest sense of the word." +"sewer" -> "Oh, our sewer system is very primitive; so primitive it's overrun by rats. But the stuff I sell is safe from them. Do you want to buy some of it?" +"king" -> "The king encouraged salesmen to travel here, but only I dared to take the risk, and a risk it was!" +"seymour" -> "He is the head of the local academy. I encouraged him to sponsor you guy, but no one listens to Obi, no one listens to me, as usually." +"hyacinth" -> "I don't like him, I dislike him deeply. He is so greedy that he doesn't want to share his profit from life fluids." +"dallheim" -> "What a hero, what a hero." +"amber" -> "She is beautiful, very, very beautiful. I hope I can impress her in some way." +"willie" -> "This guy does not understand that he should entrust me with the foodbusiness, too. He really should do that and have more time for his farm." +"bug" -> "Bugs plague this isle, but my wares are bugfree, totally bugfree." +"stuff" -> "I sell equipment of all kinds, all kind available on this isle. Just ask me about my wares if you are interested." +"tibia" -> "One day I will return to the continent as a rich, a very rich man!" +"sam" -> "My good old cousin Sam. Oh, how I miss him, how I miss him." +"thais" -> "Oh, Thais, I'll be back, I'll be back one day." + +"wares" -> "I sell weapons, shields, armor, helmets, and equipment. For what do you want to ask?" +"offer" -> * +"weapon" -> "I sell spears, rapiers, sabres, daggers, hand axes, axes, and short swords. Just tell me what you want to buy." +"armor" -> "I sell jackets, coats, doublets, leather armor, and leather legs. Just tell me what you want to buy." +"helmet" -> "I sell leather helmets, studded helmets, and chain helmets. Just tell me what you want to buy." +"shield" -> "I sell wooden shields and studded shields. Just tell me what you want to buy." +"equipment" -> "I sell torches, bags, scrolls, shovels, picks, backpacks, sickles, scythes, ropes, fishing rods and sixpacks of worms. Just tell me what you want to buy." +"do","you","sell" -> "What do you need? I sell weapons, armor, helmets, shields, and equipment." +"do","you","have" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"sickle" -> Type=3293, Amount=1, Price=8, "Do you want to buy a sickle for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"short","sword" -> Type=3294, Amount=1, Price=30, "Do you want to buy a short sword for %P gold?", Topic=1 +"jacket" -> Type=3561, Amount=1, Price=10, "Do you want to buy a jacket for %P gold?", Topic=1 +"coat" -> Type=3562, Amount=1, Price=8, "Do you want to buy a coat for %P gold?", Topic=1 +"doublet" -> Type=3379, Amount=1, Price=16, "Do you want to buy a doublet for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=25, "Do you want to buy a leather armor for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Do you want to buy leather legs for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"studded","helmet" -> Type=3376, Amount=1, Price=63, "Do you want to buy a studded helmet for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"studded","shield" -> Type=3426, Amount=1, Price=50, "Do you want to buy a studded shield for %P gold?", Topic=1 + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2853, Amount=1, Price=4, "Do you want to buy a bag for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=10, "Do you want to buy a shovel for %P gold?", Topic=1 +"pick" -> "I am sorry, we are out of pick axes. I heard that old greedy Al Dee has some but he will charge a fortune." +"backpack" -> Type=2854, Amount=1, Price=10, "Do you want to buy a backpack for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=12, "Do you want to buy a scythe for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"sickle" -> Type=3293, Amount=%1, Price=8*%1, "Do you want to buy %A sickles for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=30*%1, "Do you want to buy %A short swords for %P gold?", Topic=1 +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=10*%1, "Do you want to buy %A jackets for %P gold?", Topic=1 +%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=8*%1, "Do you want to buy %A coats for %P gold?", Topic=1 +%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=16*%1, "Do you want to buy %A dublets for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=25*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Do you want to buy %A leather legs for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=63*%1, "Do you want to buy %A studded helmets for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=50*%1, "Do you want to buy %A studded shields for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2853, Amount=%1, Price=4*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=10*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2854, Amount=%1, Price=10*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=12*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +"sell","club" -> "I don't buy this garbage!" +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","doublet" -> Type=3379, Amount=1, Price=3, "Do you want to sell a doublet for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=5, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=40, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=3, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=12, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","studded","helmet" -> Type=3376, Amount=1, Price=20, "Do you want to sell a studded helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=3, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","studded","shield" -> Type=3426, Amount=1, Price=16, "Do you want to sell a studded shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=25, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=40, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","copper","shield" -> Type=3430, Amount=1, Price=50, "Do you want to sell a copper shield for %P gold?", Topic=2 +"sell","leather","boots" -> Type=3552, Amount=1, Price=2, "Do you want to sell a pair of leather boots for %P gold?", Topic=2 +"sell","rope" -> Type=3003, Amount=1, Price=8, "Do you want to sell a rope for %P gold?", Topic=2 + +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"doublet" -> Type=3379, Amount=%1, Price=3*%1, "Do you want to sell %A doublets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=5*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=40*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=3*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=12*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","helmet" -> Type=3376, Amount=%1, Price=20*%1, "Do you want to sell %A studded helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=3*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"studded","shield" -> Type=3426, Amount=%1, Price=16*%1, "Do you want to sell %A studded shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=25*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=40*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"copper","shield" -> Type=3430, Amount=%1, Price=50*%1, "Do you want to sell %A copper shields for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","boots" -> Type=3552, Amount=%1, Price=2*%1, "Do you want to sell %A pairs of leather boots for %P gold?", Topic=2 +"sell",%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=8*%1, "Do you want to sell %A ropes for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/odemara.npc b/data/npc/odemara.npc new file mode 100644 index 0000000..01e5e90 --- /dev/null +++ b/data/npc/odemara.npc @@ -0,0 +1,70 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# odemara.npc: Datenbank für die Edelsteinhändlerin Odemara + +Name = "Odemara" +Outfit = (138,22-99-5-76) +Home = [33015,32058,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, dear %N. Have a look at our offers." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am deeply sorry, dear %N, but I am busy with a customer. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am responsible for buying and selling gems, pearls, and the like." +"name" -> "I am Odemara Taleris, it's a pleasure to meet you." +"time" -> "It's %T." +"offer" -> "We offer a great assortment of gems and pearls." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "We trade small diamonds, sapphires, rubies, emeralds, and amethysts." +"pearl" -> "We trade white and black pearls." + +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you don't have enough money." +Topic=1 -> "Too bad, perhaps we can trade on the next occasion you visit us." + +Topic=2,"yes",Count(Type)>=Amount -> "Excellent. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "I am sorry, but you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Too bad, perhaps we can trade on the next occasion you visit us." +} diff --git a/data/npc/oldadall.npc b/data/npc/oldadall.npc new file mode 100644 index 0000000..dfdc1fc --- /dev/null +++ b/data/npc/oldadall.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oldadall.npc: Datenbank für den Fährman old adall + +Name = "Old Adall" +Outfit = (130,95-26-115-76) +Home = [32628,32772,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi, %N." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Wait please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"farewell" -> * +"job" -> "I am a ferryman now, in my youth I was a sailor though. If you want to travel to one of the ends of the town, just ask me for a passage." +"sailor" -> "Aye, matey. I was a sailor and have seen much of this world." +"name" -> "My name is Adall but everyone is calling me old Adall." +"time" -> "Hehe, old men have no need for watches. Watches are mocking you, you know?" +"mocking" -> "Watches seem to tell you that not much time is left and how much time you have already lost. They only remind you of everything that is already gone and all the things you will never achieve." +"watch" -> * +"king" -> "I saw the king and even his father. Thais used the be my home port in the old times." +"venore" -> "Those newcomers have quite an attitude and it is growing worse with the years. Ambition, ambition. It's all about ambition." +"thais" -> "It used to be a lovely town but over the years it has become crowded and noisy." +"carlin" -> "Hehe, it's not such a bad town for a visit, as long as you have your own alcohol on board and are not catched drunk in the city. I had to learn it the hard way and was arrested twice in my youth." +"edron" -> "Edron is not the lovely little isle people tend to think it is. There are secrets and ancient evil beneath the ground. Things that had better been left burried, have been unearthed." +"jungle" -> "The challenge the jungle is providing is something for the young and daring. I am not going to leave the security of the town, I'll just stay here and watch how things turn out." + +"tibia" -> "The world offers much to discover. Whether you find your fortune or your doom, it is a private thing between you and fate though." + +"kazordoon" -> "I have never seen this inland town on my own." +"dwarves" -> "Some dwarves joined the colony. They are looking for treasures and minerals in the jungle." +"dwarfs" -> * +"ab'dendriel" -> "A curious town of curious people but I have seen odder things during my travels." +"elves" -> "Elves are somewhat strange but most get along well with humans." +"elfs" -> * +"darama" -> "One might think it's a strange place for an old man to settle down but I never had a child and I like to see this settlement grow and come of age in my last days ." +"darashia" -> "The people of Darashia are friendly. Still there is nothing exciting that would justify a voyage there." +"ankrahmun" -> "That city has given me the creeps for as long as I have known about it. Whenever we sailed there, I had a bad feeling of impending doom." +"ferumbras" -> "I wonder if he's the Thaian version of the boogey man." +"boogey","man" -> "The boogey man is only a myth to scare the children and keep them away from the jungle." +"excalibug" -> "I heard about that weapon in each and every harbour I have visited. Never heard more than rumours though." +"apes" -> "The apes are for Port Hope what the the orcs are for Thais." +"lizard" -> "I think they are suspicious and just because they are far away does not mean they are nice neighbours." +"dworcs" -> "They are horrible little creatures. I have seen my share of various orc breeds during my travels and those dworcs are the worst of all." + +"trip" -> "I can bring you either to the east end of Port Hope or to the west end of the town, where would you like to go?" +"route" -> * +"passage" -> * +"destination" -> * +"sail" -> * +"go" -> * + +"east" -> Price=7, "Do you seek a passage to the east end of Port Hope for %P gold?", Topic=1 +"west" -> Price=7, "Do you seek a passage to the west end of Port Hope for %P gold?", Topic=2 + + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +Topic=1,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=2,"yes",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +Topic=2,"yes",CountMoney "Sorry, you do not have enough gold." +Topic>0 -> "Maybe another time." + + +#"trip" -> Price=7, "Would you like to travel to the east end of Port Hope or to the west end of the town for 7 gold?", Topic=1 +#"route" -> * +#"passage" -> * +#"town" -> * +#"destination" -> * +#"sail" -> * +#"go" -> * +#Topic=1,"east",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +#Topic=1,"west",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +#Topic=1,"east",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32679,32777,7), EffectOpp(11) +#Topic=1,"east",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"west",Premium,CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(32558,32780,7), EffectOpp(11) +#Topic=1,"west",CountMoney "Sorry, you do not have enough gold." +#Topic=1,"yes" -> "Fine you fool, considering I was asking you WHERE you want to travel 'yes' is a very smart answer!" + +} diff --git a/data/npc/olddragon.npc b/data/npc/olddragon.npc new file mode 100644 index 0000000..34ee0f7 --- /dev/null +++ b/data/npc/olddragon.npc @@ -0,0 +1,16 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# olddragon.npc: Datenbank für den alten Drachenlord + +Name = "An Old Dragonlord" +Outfit = (39,0-0-0-0) +Home = [32796,31557,2] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",Count(3723)>=1,QuestValue(66)<1,! -> "AHHH MUSHRRROOOMSSS! NOW MY PAIN WILL BE EASSSED FOR A WHILE! TAKE THISS AND LEAVE THE DRAGONSSS' CEMETERY AT ONCE!", Amount=1, Delete(3723), Create(3206), SetQuestValue(66,1), Idle +ADDRESS,"hi$",Count(3723)>=1,QuestValue(66)<1,! -> * +ADDRESS,QuestValue(66)=1,! -> "LEAVE THE DRAGONS' CEMETERY AT ONCE!", Idle + +ADDRESS -> "AHHHH THE PAIN OF AGESSS! I NEED MUSSSSHRROOOMSSS TO EASSSE MY PAIN! BRRRING ME MUSHRRROOOMSSS!", Idle +ADDRESS -> * +} diff --git a/data/npc/oldrak.npc b/data/npc/oldrak.npc new file mode 100644 index 0000000..06199e2 --- /dev/null +++ b/data/npc/oldrak.npc @@ -0,0 +1,47 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oldrak.npc: Datenbank fuer den Moench Oldrak + +Name = "Oldrak" +Outfit = (57,0-0-0-0) +Home = [32816,32260,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Rarely I can welcome visitors in these days." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take care, it's dangerous out there." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "I guard this humble temple as a monument for the order of the nightmare knights." +"name" -> "My name is Oldrak." +"monster" -> "These plains are not safe for ordinary travellers. It will take heroes to survive here." +"help" -> "I can't help you, sorry!" +"goshnar" -> "The greatest necromant who ever cursed our land with the steps of his feet. He was defeated by the nightmare knights." +"nightmare","knights" -> "This ancient order was created by a circle of wise humans who were called 'the dreamers'. The order became extinct a long time ago." +"extinct" -> "Many perished in their battles against evil, some went mad, not able to stand their nightmares any longer. Others were seduced by the darkness." +"dreamers" -> "They learned the ancient art of dreamwalking from some elves they befriended." +"dreamwalking" -> "While the dreamwalkers of the elves experienenced the brightest dreams of pleasure, the humans strangely had dreams of dark omen." +"dark","omen" -> "They dreamed of doom, destruction, talked to dead, tormented souls, and gained unwanted insight into the schemes of darkness." +"schemes","darkness" -> "They figured out how to interpret their dark dreams and so could foresee the plans of the dark gods and their minions." +"plan","dark" -> "Using this knowledge they formed an order to thwart these plans, and because they battled their nightmares as brave as knights, they named their order accordingly." +"necromant","nectar" -> "It is rumoured to open the entrance to the pits of inferno, also called the nightmare pits. Even if I knew about this secret I wouldn't tell you." +"plains","havok" -> "Before the battles raged across them, they were called the fair plains." +"time" -> "Now, it is %T." +"tibia" -> "That's where we are. The world of Tibia." +"god" -> "They created Tibia and all life on it ... and unlife, too." +"unlife" -> "Beware the foul undead!" +"undead" -> * +"excalibug" -> "A weapon of myth and legend. It was lost in ancient times ... perhaps lost forever." +"hugo" -> "Ah, the bane of the Plains of Havoc, the hidden beast, the unbeatable foe. I live here for years and I am sure it's only a myth." +"myth",QuestValue(211)<1 -> "There are many tales about the fearsome Hugo. It's said it is an abomination, accidently created by Yenny the Gentle. It's halve demon, halve something else and people say it's still alive after dozens of years.",SetQuestValue(211,1) +"myth",QuestValue(211)>0 -> "There are many tales about the fearsome Hugo. It's said it is an abomination, accidently created by Yenny the Gentle. It's halve demon, halve something else and people say it's still alive after dozens of years." + + + +"yenny" -> "Yenny, known as the Gentle, was one of most powerfull magicwielders in ancient times and known throughout the world for her mercy and kindness." +} diff --git a/data/npc/olrik.npc b/data/npc/olrik.npc new file mode 100644 index 0000000..18220b8 --- /dev/null +++ b/data/npc/olrik.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# olrik.npc: Datenbank für den Diener und Postillion Olrik (Elfenstadt) + +Name = "Olrik" +Outfit = (128,115-79-117-76) +Home = [32675,31698,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Greetings, %N." +ADDRESS,"hello$",female,! -> "Greetings, %N. May I help you?" +ADDRESS,"hi$",male,! -> "Greetings, %N." +ADDRESS,"hi$",female,! -> "Greetings, %N. May I help you?" +ADDRESS,"ashari$",! -> "Greetings." +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "A moment please, %N.", Queue +BUSY,"hi$",male,! -> * +BUSY,"ashari$",male,! -> * +BUSY,"hello$",female,! -> "%N! A moment please, my lady. I look forward to talk to you soon.", Queue +BUSY,"hi$",female,! -> * +BUSY,"ashari$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"asha","thrazi" -> * + +"kevin" -> "He is the boss. Ther is allways some boss, so I don't bother about that." +"postner" -> * +"postmasters","guild" -> "Ah, they pay me and I do my work and thats it. Its some nice extra cash but I don't care much about that guild." +"join" -> "If you realy think you have to join the postmasters guild ask Kevin Postner at the headquarter." +"headquarter" -> "The postmasters guild headquarter is located south of the mountain known as the big old one, were the city of kazordoon can be found." + +"measurements",QuestValue(234)>0,QuestValue(240)<1 -> "My measurements? Listen, lets make that a bit more exciting ... No, no, not what you think! I mean let's gamble. I will roll a dice. If I roll a 6 you win and I'll tell you what you need to know, else I win and get 5 gold. Deal?", Amount=Random(1,6),Topic=5 +Topic=5,"no" -> "This way you'll never get my measurements." + +Topic=5,"yes",CountMoney>=5,Amount=6 -> Price=5,"Ok, here we go ... 6! You have won! How lucky you are! So listen ...",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(240,1) + +Topic=5,CountMoney>=5 -> Price=5, "Ok, and its ... %A! You have lost. He he. Another game?", DeleteMoney, Topic=6 +Topic=5,CountMoney<5 -> "I am sorry, but you don't have so much money." + +Topic=6,"yes" -> "Ok, no weights in the dice, no dirty tricks, are you ready?", Amount=Random(1,6),Topic=7 +Topic=6 -> "This way you'll never get my measurements." + +Topic=7,"yes",CountMoney>=5,Amount=6 -> Price=5,"Ok, here we go ... 6! You have won! How lucky you are! So listen ...",SetQuestValue(234,QuestValue(234)+1),SetQuestValue(240,1) + +Topic=7,"no" -> "This way you'll never get my measurements." + +Topic=7,CountMoney>=5 -> Price=5, "Ok, and its ... %A! You have lost. He he. Another game?", DeleteMoney, Topic=6 +Topic=7,CountMoney<5 -> "I am sorry, but you don't have so much money." + +"job" -> "I am a servant of the ambassador and running the post office. Ask me if you have questions about the Royal Tibia Mail System." +"name" -> "My name is Olrik." +"time" -> "It's %T." + +"elves" -> "What a noble and graceful race." +"dwarfs" -> "Uhm, let's say I prefer the company of elves." +"carlin" -> "A city full of women who surly only see whimps instead of real men like me has some appeal." +"venore" -> "Those generous merchants are charming people. I am always looking forward their visits." +"humans" -> "I feel so clumsy around those elves." +"troll" -> "What nasty creatures." +"cenath" -> "They are so wise and have an aura of mystic around them." +"kuridai" -> "They are so diligent in the things they do and such awesome fighters." +"deraisim" -> "They are so familiar with the woods and move with unparalleled grace." +"abdaisim" -> "I look forward to meet them one day." +"teshial" -> "I wonder where they have gone." +"ferumbras" -> "I heared he is dead, which is a good thing." +"crunor" -> "I honor all gods." +"tibianus" -> "He is our beloved ruler." +"roderick" -> "The ambassador is the best choice for this position of responsibility." +"excalibug" -> "Pardon?" +"news" -> "We learn few important things about the tides of time here." +"magic" -> "You should talk to the elves about that." + +@"gen-post.ndb" + +#"mail" -> "Our mail system is unique! And everyone can use it. Do you want to know more about it?", Topic=1 +#Topic=1,"yes" -> "The Tibia Mail System enables you to send and receive letters and parcels. You can buy them here if you want." +#Topic=1 -> "Is there anything else I can do for you?" + +#"letter" -> Amount=1, Price=5, "Do you want to buy a letter for %P gold?", Topic=2 +#"parcel" -> Amount=1, Price=10, "Do you want to buy a parcel for %P gold?", Topic=3 + +#Topic=2,"yes",CountMoney>=Price -> "Here it is. Don't forget to write the name of the receiver in the first line and the address in the second one before you put the letter in a mailbox.", DeleteMoney, Create(3505) +#Topic=2,"yes" -> "Oh, you do not have enough gold to buy a letter." +#Topic=2 -> "Ok." + +#Topic=3,"yes",CountMoney>=Price -> "Here you are. Don't forget to write the name and the address of the receiver on the label. The label has to be in the parcel before you put the parcel in a mailbox.", DeleteMoney, Create(3503), Create(3507) +#Topic=3,"yes" -> "Oh, you do not have enough gold to buy a parcel." +#Topic=3 -> "Ok." +} diff --git a/data/npc/omur.npc b/data/npc/omur.npc new file mode 100644 index 0000000..13dad94 --- /dev/null +++ b/data/npc/omur.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Omur.npc: Datenbank für den Gemüsehändler Omur + +Name = "Omur" +Outfit = (128,95-0-6-116) +Home = [33228,32416,7] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome at the humble booth of Omur, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please give me a minute to finish this deal, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May your soul flourish like dunegrass after a rainfall." + +"bye" -> "May your soul flourish like dunegrass after a rainfall.", Idle +"name" -> "I am called Omur." ### Ibn ??? ### +"job" -> "I sell rare fruits and vegatables from our lands and distant places." +"time" -> "Don't become a slave of a watch." +"caliph" -> "Ah, Caliph Kazzan; thrice praised be his name. May his life be as long as the beard of the king of all djinns." +"kazzan" -> * +"ferumbras" -> "I think I have heard a traveller from the west mention that name." +"excalibug" -> "Is that the name of a djinn?" +"thais" -> "We import some goods from there in exchange for ours." +"tibia" -> "The world is nothing but a vain seduction." +"carlin" -> "I know almost nothing about that town. It must be exotic and entertaining. A place of distractions from the true path." +"news" -> "Sometimes the desertwind carries the crys and mourning of the tortured souls from Drefia far into the desert." +"rumour" -> * +"desert" -> "It's not called the Devourer for nothing." + +"do","you","sell" -> "I can offer you fruits and vegetables." +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have oranges, bananas, grapes, and melons. What do you want?" +"vegetable" -> "I have carrots, pumpkins and tomatoes. What do you want?" + +"orange" -> Type=3586, Amount=1, Price=7, "Do you want to buy an orange for %P gold?", Topic=1 +"banana" -> Type=3587, Amount=1, Price=3, "Do you want to buy a banana for %P gold?", Topic=1 +"grape" -> Type=3592, Amount=1, Price=5, "Do you want to buy grapes for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=10, "Do you want to buy a melon for %P gold?", Topic=1 +"carrot" -> Type=3595, Amount=1, Price=4, "Do you want to buy a carrot for %P gold?", Topic=1 +"tomato" -> Type=3596, Amount=1, Price=5, "Do you want to buy a tomato for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + +%1,1<%1,"orange" -> Type=3586, Amount=%1, Price=7*%1, "Do you want to buy %A oranges for %P gold?", Topic=1 +%1,1<%1,"banana" -> Type=3587, Amount=%1, Price=3*%1, "Do you want to buy %A bananas for %P gold?", Topic=1 +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=5*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=10*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"carrot" -> Type=3595, Amount=%1, Price=4*%1, "Do you want to buy %A carrots for %P gold?", Topic=1 +%1,1<%1,"tomato" -> Type=3596, Amount=%1, Price=5*%1, "Do you want to buy %A tomatos for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, I'm sorry, but I can't give you credit." +Topic=1 -> "Don't you like my wares?" +} diff --git a/data/npc/oracle.npc b/data/npc/oracle.npc new file mode 100644 index 0000000..23f5fd4 --- /dev/null +++ b/data/npc/oracle.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oracle.npc: Datenbank fuer das Orakel auf Rookgaard + +Name = "The Oracle" +Outfit = (0,2031) +Home = [32104,32190,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",Level>=8,! -> "%N, ARE YOU PREPARED TO FACE YOUR DESTINY?" +ADDRESS,"hi$",Level>=8,! -> * +ADDRESS,"greet",Level>=8,! -> * +ADDRESS,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!", Idle +ADDRESS,"hi$",! -> * +ADDRESS,"greet",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Level>=8,! -> "WAIT UNTIL IT IS YOUR TURN!", Queue +BUSY,"hi$",Level>=8,! -> * +BUSY,"greet",Level>=8,! -> * +BUSY,"hello$",! -> "CHILD! COME BACK WHEN YOU HAVE GROWN UP!" +BUSY,"hi$",! -> * +BUSY,"greet",! -> * +BUSY,! -> NOP +VANISH,! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!" + +"yes",premium -> "IN WHICH TOWN DO YOU WANT TO LIVE: CARLIN, EDRON, THAIS, OR VENORE?", Topic=1 +"yes" -> "IN WHICH TOWN DO YOU WANT TO LIVE: CARLIN, THAIS, OR VENORE?", Topic=1 +"bye",! -> "COME BACK WHEN YOU ARE PREPARED TO FACE YOUR DESTINY!", Idle + -> * + +Topic=1,"thais" -> Data=1, "IN THAIS! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"carlin" -> Data=2, "IN CARLIN! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"venore" -> Data=3, "IN VENORE! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"edron",premium -> Data=4, "IN EDRON! AND WHAT PROFESSION HAVE YOU CHOSEN: KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 +Topic=1,"edron" -> "YOU NEED A PREMIUM ACCOUNT IN ORDER TO GO THERE!", Topic=1 +Topic=1,premium -> "CARLIN, EDRON, THAIS, OR VENORE?", Topic=1 +Topic=1 -> "CARLIN, THAIS, OR VENORE?", Topic=1 + +Topic=2,"knight" -> Type=4, "A KNIGHT! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"paladin" -> Type=3, "A PALADIN! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"sorcerer" -> Type=1, "A SORCERER! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2,"druid" -> Type=2, "A DRUID! ARE YOU SURE? THIS DECISION IS IRREVERSIBLE!", Topic=3 +Topic=2 -> "KNIGHT, PALADIN, SORCERER, OR DRUID?", Topic=2 + +Topic=3,Data=1,"yes" -> "SO BE IT!", Profession(Type), Town(1), Idle, EffectOpp(11), Teleport(32369,32241,7), EffectOpp(11) +Topic=3,Data=2,"yes" -> "SO BE IT!", Profession(Type), Town(2), Idle, EffectOpp(11), Teleport(32360,31782,7), EffectOpp(11) +Topic=3,Data=3,"yes" -> "SO BE IT!", Profession(Type), Town(7), Idle, EffectOpp(11), Teleport(32957,32076,7), EffectOpp(11) +Topic=3,Data=4,"yes" -> "SO BE IT!", Profession(Type), Town(5), Idle, EffectOpp(11), Teleport(33217,31814,8), EffectOpp(11) +} diff --git a/data/npc/orcking.npc b/data/npc/orcking.npc new file mode 100644 index 0000000..0998fc1 --- /dev/null +++ b/data/npc/orcking.npc @@ -0,0 +1,56 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# orcking.npc: Datenbank für den Orckönig + +Name = "The Orc King" +Outfit = (19,0-0-0-0) +Home = [32983,31728,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$", QuestValue(218) = 0,! -> "Arrrrgh! A dirty paleskin! To me my children! Kill them my guards!",SetQuestValue(218,1),Summon("Orc Leader"),Summon("Orc Leader"),Summon("Orc Leader"),Summon("Orc Warlord"),Summon("Orc Warlord"),Summon("Slime"),Summon("Slime"),Summon("Slime"),Idle + +ADDRESS,"hi$", QuestValue(218) = 0,! -> * +ADDRESS,"hello$",! -> "Harrrrk! You think you are strong now? You shall never escape my wrath! I am immortal!" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Harrrk!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yes, flee this place, but you will never escape my revenge!" + + +"bye" -> "We will meet again.", Idle +"farewell" -> * + +"name" -> "I am Charkahn the Slayer! The immortal father of the orcs and master of this hive." +"job" -> * +"hive" -> "I can sense the presence and the feelings of my underlings and minions. I embrace the rage of the horde." +"minion" -> "The orcish horde of this hive is under my control. I sense their emotions and their needs and provide them with the leadership they need to focus their hate and rage." +"underling" -> * +"horde" -> * +"hate" -> "Hate and rage are the true blessings of Blog, since they are powerful weapons. They give the hive strength. I provide them with direction and focus." +"rage" -> * +"direction" -> "To conquer, to destroy and to dominate. Orcs are born to rule the world." +"focus" -> * +"blog" -> "The Raging One blessed us with his burning hate. We are truly his children and therefore divine." +"divine" -> "The orcs are the bearers of Blogs rage. This makes us the ultimate fighters and the most powerful of all races." +"orc" -> * +"slime" -> "Pah! Don't mock me, mortal! This shape is a curse which the evil djinn bestowed upon me!" +"djinn" -> "This cursed djinn king! I set him free from an enchanted lamp, and he cheated me!" +"malor" -> * +"cheat" -> "Because I freed him he granted me three wishes. He was true to his word in the first two wishes." +"wish" -> "He built this fortress over Uldrek's grave within a single night. Also, he granted me my second wish and gave me immortality. Test it and try to kill me if you want. Har Har!" +"third" -> "I wished to father more healthy and fertile children as any orc has ever done. But the djinn cheated me and made me a slime! Then he laughed at me and left for his abandoned fortress in the Deathwish Mountains." +"deathwish" -> "His ancient fortress on Darama was deserted as the evil Djinn fled this world after his imprisonment. Now the time has come for the evil Djinns to return to their master although this will certainly awaken the good Djinn too." +"abandoned" -> * +"good","djinn" -> "I will not share anything more about that topic with you paleskins." +"awaken" -> * +"paleskins" -> "You are as ugly as maggots, although not quite as as tasty." +"lamp" -> "For Eons he was trapped in an enchanted lamp by some ancient race. Now he's free to roam the world again. Although he cheated me I appreciate what he and his brethren will do to this world, now it's the time of the Djinn again!" +"lamp",QuestValue(283)=1,QuestValue(284)=0,! -> "I can sense your evil intentions to imprison a djinn! You are longing for the lamp, which I still possess. ...", + "Who do you want to trap in this cursed lamp?",Topic=1 +"lamp",QuestValue(288)=1,QuestValue(284)=0,! -> * +Topic=1,"malor",! -> "I was waiting for this day! Take the lamp and let Malor feel my wrath!",Amount=1,Create(3231),SetQuestValue(284,1) +Topic=1 -> "I don't know your enemy, paleskin! Begone!", Idle +} diff --git a/data/npc/ormuhn.npc b/data/npc/ormuhn.npc new file mode 100644 index 0000000..31e8403 --- /dev/null +++ b/data/npc/ormuhn.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ormuhn.npc: Datenbank für die arenaleiter ormuhn + +Name = "Ormuhn" +Outfit = (18,0-0-0-0) +Home = [33160,32810,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am the arena master. I supervise all challenges that take place in this arena and train true fighters." +"name" -> "I am called Ormuhn." +"time" -> "Time only matters to you while you are mortal. Another instrument in the hands of the false gods to fool us all." +"temple" -> "The temple takes care of your Uthun. In this arena we challenge your Akh." +"pharaoh" -> "The pharaoh, our mighty leader, is an unliving god." +"oldpharaoh" -> "The pharaoh will know why he granted him a chance to ascend." +"scarab" -> "Scarabs might be sacred, but they are also a challenge. If you are able to overcome one of them, its spirit will forgive you. The everlasting sand will grant him rebirth anyway." +"chosen" -> "I am one of the chosen. To become like me you have to serve the pharaoh and his temple faithfully." + +"tibia" -> "Tibia is a place kept in the thrall of the greedy false gods. One day though our pharaoh will free Tibia and guide all of its people to ascension." + +"carlin" -> "These cities are nests of corruption and lies. For those who know the treason of the false gods is almost tangible there." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves are worthy warriors. Still their mortal Akh makes them prey to true death and so their lives are wasted." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are nothing but feeble treehuggers." +"elves" -> * +"elfes" -> * +"darama" -> "This continent will be the first to prosper under the guidance of our pharaoh." +"darashia" -> "The people there are not totally lost to the false gods yet. Who knows? They may be saved yet." +"daraman" -> "A mere mortal prophet. As was to be expected, his mortality blurred his visions of ascension." +"ankrahmun" -> "This is the shelter of the mortal flock who listens to the teachings of our pharaoh." + +"mortality" -> "Your curse of mortality can be lifted if you only prove youself worthy in the eyes of our pharaoh." +"false", "gods" -> "These greedy reapers of souls are the true scourge of poor mortals like you. Your living Akh makes you vulnerable for their attacks. Withstand them and you will get a chance to be raised to the exalted state of undeath." + +"ascension" -> "Ascension is achieved in many steps. The first and most important step is unquestioning loyal service to our pharaoh." +"Akh'rah","Uthun" -> "You should discuss such topics with our priests. I don't care too much for these matters." +"Akh" -> "As far as I know this is what you would call your body. Ask a priest for further information." +"undead" -> "We are the chosen ones." +"undeath" -> * +"Rah" -> "That is your so-called soul. Ask a priest for further information." +"uthun" -> "All the things you remember form your uthun. Ask a priest for further information." +"mourn" -> "Living flesh is so ... pathetic." + +"arena" -> "If you wish to test your mortal Akh you are at the right place." +"palace" -> "The palace is guarded by the elite forces of the chosen." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "May enlightenment be your path.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/data/npc/oswald.npc b/data/npc/oswald.npc new file mode 100644 index 0000000..7d7eff5 --- /dev/null +++ b/data/npc/oswald.npc @@ -0,0 +1,68 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# oswald.npc: Datenbank für Durin-Helfer Oswald + +Name = "Oswald" +Outfit = (128,115-0-67-114) +Home = [32381,32220,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Oh, hello %N. What is it?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Be patient, draw a number, %N.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, and don't come back too soon." + +"bye",Level>0 -> "Finally!", Idle +"farewell",Level>0 -> * +"bye",Level<2 -> "Good bye, master %N. Have a nice day!", Idle +"farewell",Level<2 -> * +"how","are","you"-> "If there weren't so many people harassing me, life could be great." +"sell" -> "Hey, I am not a shopkeeper, I am an important man!" +"harassing" -> "You need not ask me about that, you are perfect in that." +"job" -> "I am honored to be the assistant of the great, the illustrious, the magnificent Durin!" +"durin" -> "Just between you and me, he can be quite a tyrant." +"important" -> "I am honored to be the assistant of the great, the illustrious, the magnificent Durin!" +"name" -> "My name is Oswald, but let's proceed, I am a very busy man." +"time" -> "It is nearly tea time, so please hurry!" +"help" -> "I inform higher officials of your need... sometimes." +"monster" -> "AHHHH!!! WHERE??? WHERE???", Idle +"dungeon" -> "If you want to see dungeons just don't pay your taxes." +"sewer" -> "Our sewer system is very modern, but crowded with rats and wannabe heroes." +"assistant" -> "I have a job of great responsibility, mostly I keep annoying persons away from my boss." +"annoying" -> "You better don't ask, you wouldn't like the answer." +"thank","you" -> "You are... uhm... welcome. Are you finished already?" +"god" -> "I think the gods are too busy to care about us mortals, hmm... that makes me feel godlike, too." +"king" -> "Ah, yes, yes, hail to King Tibianus! Long live the king and so on..." +"sam" -> "A simple shopkeeper with minor intelligence." +"benjamin" -> "What do you expect from ex-soldiers? He is nuts! Hacked on the head far too often." +"gorn" -> "He sells his scrolls far too expensive." +"quentin" -> "I heard he was a ladies' man in younger days. In our days he is rumoured to wear women clothes now and then." +"bozo" -> "Isn't he the artist formerly known as the prince?" +"rumour" -> "You know a rumour? TELL ME! TELL ME! TELL ME!", Topic=3 +"gossip" -> * +"news" -> * +"mud" -> "I heared Sam dated a female mud-wrestler once." +"weapon" -> "It's rumoured that Sam does not forge all weapons himself, but buys them from his cousin, who is married to a cyclops." +"magic" -> "I overheard a conversation of officials, that magic will be forbidden soon." +"power" -> "There are people who talk about a rebellion against King Tibianus." +"rebellion" -> "There are people who talk about a rebellion against King Tibianus." +"spell" -> "I was told sometimes that sorcerers are toasted by misfired spells of their own." +"muriel" -> "He is rumoured to summon kinky demons to... well you know." +"elane" -> "They say she killed over a dozen husbands already." +"marvik" -> "Who knows what this old man is up to in his hideout when no one is watching?" +"gregor" -> "I was told he lost a body part or two in duels... if you know what I mean." +"lugri" -> "Some say he is Ferumbras in disguise." +"excalibug" -> "It's beyond all doubt that certain sinister elements in our city have certain knowledge about this myth." +"chester" -> "I never found any rumour concerning him, isn't that odd?" +"ardua" -> "She's a bitch, trust me. She was the girlfriend of the evil Partos some time ago." +"partos" -> "What a shame. He claimed to be the king of thiefs and was caught stealing some fruit." +"gamel" -> "This man lives in the darkness like a rat and is also as handsome as one of them. He surely is up to no good and often consorts with sinister strangers." +"sinister","strangers" -> "Just last week a one eyed man, who had a room at Frodo's, met him in the middle of the night." +"goshnar" -> "They say he isn't truly dead. He was... or is a necromant after all." +"necromant","nectar" -> "You are not the first one to ask about that. Am I the only one that preferes wine to such disgusting stuff?" + +Topic=3 -> "Fascinating! Absolutely fascinating!" +} diff --git a/data/npc/padreia.npc b/data/npc/padreia.npc new file mode 100644 index 0000000..c5d6983 --- /dev/null +++ b/data/npc/padreia.npc @@ -0,0 +1,137 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# padreia.npc: Datenbank fuer die Druidin Padreia + +Name = "Padreia" +Outfit = (138,0-87-85-95) +Home = [32300,31816,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",Druid,! -> "Crunor's blessings. I am glad to see you again, %N!" +ADDRESS,"hi$",Druid,! -> "Crunor's blessings. I am glad to see you again, %N!" +ADDRESS,"hello$",! -> "Welcome to our humble guild, wanderer. May I be of any assistance to you?" +ADDRESS,"hi$",! -> "Welcome to our humble guild, wanderer. May I be of any assistance to you?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> "Please wait, %N.", Queue +BUSY,! -> NOP +VANISH,Druid,! -> "Farewell, %N. May Crunor be with you, my child." +VANISH,! -> "Farewell. May Crunor be with you." + +"bye",Druid -> "Farewell, %N.", Idle +"farewell",Druid-> * +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am the grand druid of Carlin. I am responsible for the guild, the fields, and our citizens health." +"grand","druid" -> * +"name" -> "I am Padreia, Grand Druid of our fine city." +"time" -> "Time is just a crystal pillar. The center of creation and life." +"member" -> "Our members wield magic powers of protection and healing." +"magic" -> "Every member of the Druids is able to learn the numerous spells of our craft." +"power" -> * +"druid" -> "We are druids, preservers of life. Our magic is about defense, healing, and nature." +"sorcerer" -> "Sorcerers are destrucitve. Their power lies in destruction and pain." +"vocation" -> "Your vocation is your profession. There are four vocations in this world: Druids, paladins, knights, and sorcerers." +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit Rachel." +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +"crunor","caress" -> "Don't ask. They were only an unimportant footnote of history." +"footnote",QuestValue(211)=2 -> "They thought they have to bring Crunor to the people, if people did not find to Crunor of their own. To achieve that they founded the inn Crunor's Cottage, south of Mt. Sternum.",SetQuestValue(211,3) +"footnote",QuestValue(211)<2 -> "I have to attend other business, ask later please." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Farewell.", Idle + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +"cough", "syrup" -> Type=4828, Price=50, "Do you want to buy a bottle of cough syrup for %P gold?", Topic=10 +Topic=10,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "Sorry, you do not have enough gold." +Topic=10 -> "Maybe you will need it another time." + + +} diff --git a/data/npc/partos.npc b/data/npc/partos.npc new file mode 100644 index 0000000..77f9a6a --- /dev/null +++ b/data/npc/partos.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# partos.npc: Datenbank für den Sträfling Partos + +Name = "Partos" +Outfit = (128,116-56-95-122) +Home = [32323,32280,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little kingdom, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, don't go away, I am ready soon and don't get visitors often.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I wish I could do that, too." + +"bye" -> "Good bye, visit me again. I will be here, promised.", Idle +"farewell" -> * +"job" -> "Guess it! I give you a hint: I am not in this cell to clean it up! ...", + "I wished, I would have never left Ankrahmun." +"news" -> "I hardly hear any news down here." +"name" -> "My name is Partos, but you can call me Party." +"party" -> "Yeah! Come in and let's have a party." +"thais" -> "I love the city. I just wish I could see some other part of it now and then." +"city" -> * +"tibia" -> "I love this world. I just wish I could see some other part of it now and then." +"how","are","you"-> "I am great! Free food, free room, and now and then someone coming down here to ask me silly questions. Wouldn't you love that, too?" +"sell" -> "I would like to sell you a secret, but I'm out of business for too long." +"jail" -> "You mean that's a JAIL? They told me it's the finest hotel in town! THAT explains the lousy roomservice!" +"prison" -> * +"crime" -> "Bah, I did nothing serious. I just had a little fun. In Ankrahmun nobody would have cared about these kind of things..." +"criminal" -> * +"god$" -> "The gods seldom show up down here, so don't ask me." +"gods$" -> * +"citizen" -> "Rich enough to spare a little, don't you agree? Well, they didn't agree." +"king" -> "Yeah, a king is a man that can rob people by law, and not by night like me." +"monster" -> "At least I am safe from them down here." +"gold" -> "Gold got me in here." +"money" -> * +"fight" -> "Hey, most people I killed were even worse than me." +"slay" -> * +"noodles" -> "I bet one could get some fine ransom, if he dognappes this furball." +"quentin" -> "By the gods, he visits us 'criminals' now and then to 'save' us. Who is going to save me from this boredom on two legs?" +"army" -> "Bah, the king's pawns. I spit on them." +"time" -> "Geee, someone stole my watch. Bad company down here." +"ankrahmun" -> "Yes, I've lived in Ankrahmun for quite some time. Ahh, good old times! ...", + "Unfortunately I had to relocate. ...", + "Business reasons - you know." + +"waterpipe" -> "My waterpipe? I lost it. But it doesn't matter. I quit smoking anyway." +"djinn",QuestValue(286)=1,! -> "What!? I bet, Baa'leal sent you! ...", + "I won't tell you anything! Shove off!", SetQuestValue(286,2), Idle +"baa'leal",QuestValue(286)=1,! -> * +"supplies",QuestValue(286)=1,! -> * +"mal'ouquah",QuestValue(286)=1,! -> * + +"djinn" -> "I won't talk about that." +"baa'leal" -> * +"supplies" -> * +"mal'ouquah" -> * + +#"excalibug" -> "Excalibug? No way that I tell you something about it!" +#"grapes" -> Type=3592, Amount=1, "Do you have any grapes with you?", Topic=1 + +#Topic=1,"yes",Count(Type)>=Amount -> "What do you want for that ...ohhh... tasty ...uhm... sweet ...drool... delicous ...hmm... grapes?", Delete(Type), Topic=2 +#Topic=1,"yes" -> "Go away, if you don't have any grapes." +#Topic=1 -> * +#Topic=2,"excalibug" -> "My late mentor once told me he found a wallcarving about this sword in a cave beneath the castle.", Topic=2 +#Topic=2,"wallcarving" -> "That part of the dungeon was recently blocked by a cave-in. It was unsecure before, and only a fool would have entered there. I stayed out and alive." +} diff --git a/data/npc/pemaret.npc b/data/npc/pemaret.npc new file mode 100644 index 0000000..1e889ac --- /dev/null +++ b/data/npc/pemaret.npc @@ -0,0 +1,56 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pemaret.npc: Fischer Pemaret auf Cormaya + +Name = "Pemaret" +Outfit = (128,79-10-127-127) +Home = [33287,31956,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Greetings, young man. Looking for a passage or some fish, %N?" +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Greetings, young lady. Looking for a passage or some fish, %N?" +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Pemaret, the fisherman." +"job" -> "I'm a fisherman and I take along people to Edron. You can also buy some fresh fish." +"tibia" -> "I love to sail on the seas of Tibia." +"sea" -> * +"cormaya" -> "It's a lovely and peaceful isle. Did you already visit the nice sandy beach?" +"isle" -> * +"beach" -> "There is a nice sandy beach in the west of Cormaya." + +"ship" -> "My boat is ready to bring you to Edron." +"boat" -> * +"passage" -> * + +"edron" -> Price=20, "Do you want to get to Edron for %P gold?", Topic=1 +"edron",QuestValue(250)>2 -> Price=10, "Do you want to get to Edron for %P gold?", Topic=1 + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy a fresh fish for %P gold?", Topic=2 +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fresh fishes for %P gold?", Topic=2 + +"eremo" -> "Oh, you know the good old sage Eremo. I can bring you to his little island. Do you want me to do that?", Topic=3 +"sage" -> * + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=3,"yes",PZBlock,! -> * + +Topic=1,"yes",CountMoney>=Price -> "Here we go!", DeleteMoney, Idle, EffectOpp(11), Teleport(33175,31764,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Maybe later." + +Topic=2,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "I am sorry, but you do not have enough gold." +Topic=2 -> "Maybe later." + +Topic=3,"yes" -> "Here we go!", Idle, EffectOpp(11), Teleport(33315,31882,7), EffectOpp(11) +Topic=3 -> "Maybe later." +} diff --git a/data/npc/penny.npc b/data/npc/penny.npc new file mode 100644 index 0000000..51a2c7d --- /dev/null +++ b/data/npc/penny.npc @@ -0,0 +1,31 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# penny.npc: Datenbank für die GM-Gehilfin Penny + +Name = "Penny" +Outfit = (137,96-79-95-96) +Home = [32315,31936,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",male,! -> "Welcome home, Sir %N." +ADDRESS,"hi$",male,! -> * +ADDRESS,"hello$",female,! -> "Welcome home, Lady %N." +ADDRESS,"hi$",female,! -> * +ADDRESS,! -> Idle +BUSY,"hi$",male,! -> "Just a minute, Sir %N.", Queue +BUSY,"hello$",male,! -> * +BUSY,"hi$",female,! -> "Just a minute, Lady %N.", Queue +BUSY,"hello$",female,! -> * +BUSY,! -> NOP +VANISH,! -> "May Justice be with you!" + +"bye" -> "May Justice be with you!", Idle +"farewell" -> * +"name" -> "I am miss Penny, your secretary." +"job" -> "I'm your secretary. I'm organizing all those criminal records and your mail." +"criminal" -> " It's an evil world, isn't it?" +"record" -> * +"mail" -> "You can get a letter from me." +"letter" -> "Here you are.", Create(3505) + +} diff --git a/data/npc/perac.npc b/data/npc/perac.npc new file mode 100644 index 0000000..ee101f2 --- /dev/null +++ b/data/npc/perac.npc @@ -0,0 +1,48 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# perac.npc: Datenbank für den Bogner Perac + +Name = "Perac" +Outfit = (129,78-52-68-114) +Home = [32295,31784,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to a customer." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am the fletcher of Carlin. I am selling bows, crossbows, and ammunition. Do you need anything?" +"name" -> "I am Perac, fletcher and marksman extraordinaire." +"marksman" -> "I am a paladin and the best marksman in the land." +"time" -> "Don't bother me. Go and buy a watch." +"ghostlands" -> "I was there ... once. I got out before the illusions drove me mad. Better stay out of that area!" + + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow or bolts for a crossbow?" +"ammunition" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/perod.npc b/data/npc/perod.npc new file mode 100644 index 0000000..1a3139e --- /dev/null +++ b/data/npc/perod.npc @@ -0,0 +1,141 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# perod.npc: Datenbank für den Händler Perod + +Name = "Perod" +Outfit = (128,58-68-12-114) +Home = [32635,32739,5] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, dear %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N. I am already talking to someone.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I sell general goods which are, if I am allowed to say that, crucial when you explore the jungle." +"name" -> "I am Perod, how could you forget that, %N? We fought back-to-back in those troll caves on Rookgard a long time ago." +"time" -> "I won't tell you, but you can buy one of my quality watches to find out." +"king" -> "The king is far away and yet we are still his subjects. Strange, isn't it?" +"venore" -> "After I had left Thais I found a new home in Venore and I never regreted it." +"thais" -> "Thais lacked any prospect for a change. I quickly figured out that Venore is the place to be." +"carlin" -> "Carlin is a dull city with strange habits." +"edron" -> "I lived in Edron as a treasure hunter for a while, but then the place became too crowded." +"jungle" -> "The jungle is full of adventures and secrets that wait to be explored. Some years ago I would have surely enjoyed that. ...", + "But now that I setteled down here, I don't feel excited anymore by the thought of exploring an inhospitable forest full of animals that want to kill me." + +"tibia" -> "I have travelled a lot and still I have not seen everything. So I abandoned my life as an explorer and became an employee of a trading company domiciled in Venore." + +"kazordoon" -> "The hidden city of the dwarves can be quite confusing for a newcomer. I got lost there a dozen times before I became familiar with that city." +"dwarves" -> "Some dwarves live in the city, you'll find them in the tavern." +"dwarfs" -> * +"ab'dendriel" -> "The elves of Ab'Dendriel built a city seemingly out of trees. I wonder how they can stand the winter in those odd houses." +"elves" -> * +"elfs" -> * +"darama" -> "A new continent means new chances. But my days as an adventurer are over. My new chances lie in trade and commerce." +"darashia" -> "My trips there were very short, I don't like the desert and I did not like that town." +"ankrahmun" -> "It's one of the few cities I have never visited and no one will ever get me even close to that city of undeads and mummies." +"ferumbras" -> "During my days as an adventurer, I was thrilled by the thought to fight him. Looking back I must say it is better that I have never met him." +"excalibug" -> "Oh boy, how long have we searched for that weapon. I still wonder sometimes where it might be hidden, but I have no clue." +"apes" -> "Their occasional raids give me a chance to train my fighting skills." +"lizard" -> "I bet those lizards hide some ancient treasures in their settlements." +"dworcs" -> "It's the jungle variant of the orcs. I guess no matter where you go, there is always some orc waiting behind some bush, ready to thrust his blade in your body." + + +"offer" -> "I offer fishing rods, sixpacks of worms, shovels, picks, scythes, bags, ropes, backpacks, plates, jugs, mugs, cups, bottles, buckets, scrolls, documents, parchments, footballs, watches, books, torches, machetes, presents and ammunition." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * + + +"magic" -> "Ask somwhere else in the market." +"fluid" -> * + +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"bag" -> Type=2864, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2872, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"bucket" -> Type=2873, Amount=1, Price=4, "Do you want to buy a bucket for %P gold?", Topic=1 +"bottle" -> Type=2875, Amount=1, Price=3, "Do you want to buy a bottle for %P gold?", Topic=1 +"mug" -> Type=2880, Amount=1, Price=4, "Do you want to buy a mug for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=2, "Do you want to buy a cup for %P gold?", Topic=1 +"jug" -> Type=2882, Amount=1, Price=10, "Do you want to buy a jug for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=40, "Do you want to buy a machete for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2864, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2872, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"bucket" -> Type=2873, Amount=%1, Price=4*%1, "Do you want to buy %A buckets for %P gold?", Topic=1 +%1,1<%1,"bottle" -> Type=2875, Amount=%1, Price=3*%1, "Do you want to buy %A bottles for %P gold?", Topic=1 +%1,1<%1,"mug" -> Type=2880, Amount=%1, Price=4*%1, "Do you want to buy %A mugs for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=2*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"jug" -> Type=2882, Amount=%1, Price=10*%1, "Do you want to buy %A jugs for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=40*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms would you like to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + + +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=2 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=2 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=2 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=2 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=2 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=2 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=2 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=2 + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, but next time." + +} diff --git a/data/npc/phillip.npc b/data/npc/phillip.npc new file mode 100644 index 0000000..79506f0 --- /dev/null +++ b/data/npc/phillip.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# phillip.npc: Datenbank für den Lehrer Phillip + +Name = "Phillip" +Outfit = (128,116-54-68-76) +Home = [32369,31764,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",Level<4,! -> "Hello, pupil %N. I hope I can help you on your quest for knowledge." +ADDRESS,"hi$",Level<4,! -> * +ADDRESS,"hello$",Level<15,! -> "Hello, seeker of knowledge %N. How may I assist you?" +ADDRESS,"hi$",Level<15,! -> * +ADDRESS,"hello$",Level<25,! -> "Hello, mighty adventurer %N. Can I teach you something you don't know?" +ADDRESS,"hi$",Level<25,! -> * +ADDRESS,"hello$",! -> "Hello, famous %N. It should be you teaching me!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Patience, %N. Listen to my words and learn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Did the bell ring?" + +"bye" -> "Go and be careful. Remember what you have learned!", Idle +"farewell" -> * +"how","are","you"-> "I am fine, thank you very much." +"sell" -> "My business is knowlegde and it is for free." +"job" -> "I am honored to be teacher in this school." +"teacher" -> "I run this school, there are other travelling teachers who we call Loremasters." +"loremaster" -> "If you are lucky you'll meet one in your journeys." +"name" -> "My name is Phillip." +"time" -> "It is %T." +"help" -> "I will provide you with all knowledge I have." +"monster" -> "Monsters come in different shape and power. It's said there is a zoo in the dwarfs' town." +"dungeon" -> "Dungeons are places of danger and puzzles. In some of them a bright mind will serve you more then a blade." +"sewer",female -> "The sewers of Carlin are a disgusting place. Better never crawl around in these stinking tunnels." +"sewer",male -> "An interesting place you should consider to visit." +"thank","you" -> "You don't have to thank me, it's only my duty." +"god" -> "To learn about gods, visit the temples and talk to the priests." +"king" -> "The southern king is called Tibianus. He and our queen Eloise are in a constant struggle." +"queen" -> * +"rumour" -> "I don't like rumours." +"gossip" -> * +"news" -> * +"weapon" -> "To learn about weapons read appropriate books or talk to the smiths." +"magic" -> "To learn about magic talk to the guild leaders." +"rebellion" -> "Rebellion? What for? We are contend with our situation." +"in","tod","we","trust" -> "Tod will come and save us all. He will bring freedom and beer to the men of Carlin." +"lugri" -> "This servant of evil is protected by the dark gods and can't be harmed." +"ferumbras" -> "He is a follower of evil. His powers were boosted by a sinister force and he is beyond human restrictions now." +"excalibug" -> "This weapon is said to be very powerful and unique. It was hidden in ancient times and now is thought to be lost." +} diff --git a/data/npc/pino.npc b/data/npc/pino.npc new file mode 100644 index 0000000..53eb050 --- /dev/null +++ b/data/npc/pino.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pino: Datenbank für den Teppichpiloten Pino in Edron + +Name = "Pino" +Outfit = (128,115-0-67-114) +Home = [33192,31783,3] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye!" + +"bye" -> "Good bye!", Idle +"name" -> "Pino at your service." +"job" -> "I am a carpetpilot. I can fly you to the Femor Hills or Darashia." +"service" -> * +"time" -> "It's %T right now." +"tibia" -> "What a wonderful world. Especially if you look down on it." + +"passage" -> "I can fly you to Darashia on Darama or to the Femor Hills if you like. Where do you want to go?" +"transport" -> * +"ride" -> * +"trip" -> * + +"darashia" -> Price=40, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama" -> * +"hill" -> Price=60, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=2 +"femor" -> * +"femur" -> "You are probably talking about the FEMOR hills." + +"darashia",QuestValue(250)>2 -> Price=30, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama",QuestValue(250)>2 -> * +"hill",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to the Femor Hills for %P gold?", Topic=2 +"femor",QuestValue(250)>2 -> * + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * + +Topic=1,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +Topic=2,"yes",CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(32535,31837,4), EffectOpp(11) +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." +} diff --git a/data/npc/prisoner.npc b/data/npc/prisoner.npc new file mode 100644 index 0000000..1bfc4b7 --- /dev/null +++ b/data/npc/prisoner.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# prisoner.npc: Datenbank für den Prisoner alias Mad Mage + +Name = "A Prisoner" +Outfit = (130,81-40-55-94) +Home = [32393,32137,13] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Huh? What? I can see! Wow! A non-mino. Did they capture you as well?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not much space right here. Hehe. I haven´t had a visitor for some time and right now there are two! Hehehe! Great!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,QuestValue(1)>0,! -> "Good bye! Don't forget about the secrets of mathemagics." +VANISH,QuestValue(1)=0,! -> "Wait! Don't leave! I want to tell you about my surreal numbers." + + +"bye" -> "Next time we should talk about my surreal numbers.", Idle +"farewell" -> * +"job" -> "Job? JOB? Hey man - I am in prison! But you know - once upon a time - I was a powerful mage! A mage ... come to think of it .., what is that - a mage?" +"name" -> "My name is - uhm - hang on? I knew it yesterday, didn't I? Doesn't matter!" +"time" -> "Better save time than comitting a crime. I am a poet and I know it!" +"sorcerer" -> "I am the mightiest sorcerer from here to there! Yeah!" +"power" -> "Power. Hmmm. Once while we were crossing the mountains together a man named Aureus said to me that parcels are equal to power. Any idea what that meant?" +"books" -> "I have many books in my home. But only powerful people can read them. I bet you will only see three dots after the headline! Hehehe! Hahaha! Excellent!" +"mad","mage" -> "Hey! That's me! You got it! Thanks mate - now I remember my name!" +"riddle" -> "Great riddle, isn´t it? If you can tell me the correct answer, I will give you something. Hehehe!" +"something" -> "No! I won´t tell you. Shame coz it would be useful for you - hehehe." +"apple" -> Type=3585, Amount=1, "Apples! Real apples! Man I love them! Can I have one? Oh please say yes!", Topic=1 +"escape" -> "How could I escape? They only give me rotten food here. I can´t regain my powers because I have no mana!" +"key" -> "Sure I have the key! Hehehe! Perhaps I will give it to you. IF you can solve my riddle." +"mino" -> "They are trying to capture me! Or hang on! Haven't they already captured me? Hmmm - I will have to think about this." +"markwin" -> "He is the worst of them all! He is the king of the minos! May he burn in hell!" +"labyrinth" -> "It´s easy to find your way through it! Just follow the pools of mud. Hehe - useful hint, isn´t it?" +"way" -> * +"palkar" -> "He is the leader of the outcasts. I hope he will never conquer the city of Mintwallin. That would be the end of me!" +"karl" -> "Tataah!" +"demon" -> "The only monster I cannot conjure. But soon I will be powerful enough!" +"monster" -> "Yeah! There are many monsters guarding my home. Only the bravest hero will be able to slay them!" +"conjure" -> * +"home" -> * + +"number", QuestValue(1) < 1 -> "My surreal numbers are based on astonishing facts. Are you interested in learning the secret of mathemagics?", Topic=7, Amount=Random(1,4) +"math", QuestValue(1) < 1 -> * +"1+1$", QuestValue(1) < 1 -> * +"1$","+$","1$", QuestValue(1) < 1 -> * +"1$","plus","1$", QuestValue(1) < 1 -> * +"one","plus","one", QuestValue(1) < 1 -> * + +"number", QuestValue(1) > 0 -> "You already know the secrets of mathemagics! Now go and use them to learn." +"math", QuestValue(1) > 0 -> * +"1+1$", QuestValue(1) > 0 -> * +"1$","+$","1$", QuestValue(1) > 0 -> * +"1$","plus","1$", QuestValue(1) > 0 -> * +"one","plus","one", QuestValue(1) > 0 -> * +Topic=7, "yes" -> "But first tell me your favourite colour please!", Topic=8 +Topic=8,QuestValue(2)=1,"red",! -> "Very interesting. So are you ready to proceed in you lesson in mathemagics?",Topic=9 +Topic=8,QuestValue(2)=2,"blue",! -> * +Topic=8,QuestValue(2)=3,"black",! -> * +Topic=8,QuestValue(2)=4,"white",! -> * +Topic=8,QuestValue(2)=5,"orange",! -> * +Topic=8,QuestValue(2)=6,"green",! -> * +Topic=8,QuestValue(2)=7,"yellow",! -> * +Topic=8,QuestValue(2)=8,"brown",! -> * +Topic=8,QuestValue(2)=9,"violet",! -> * +Topic=8,QuestValue(2)=10,"pink",! -> * +Topic=8,QuestValue(2)=11,"silver",! -> * +Topic=8,QuestValue(2)=12,"gold",! -> * +Topic=8,QuestValue(2)=13,"grey",! -> * + +Topic=8,! -> "I think you are not in touch with yourself, come back if you have tuned in on your own feelings." + +Topic=9, "yes", Amount=1 -> "So know that everthing is based on the simple fact that 1 + 1 = 49!", SetQuestValue(1,1) +Topic=9, "yes", Amount=2 -> "So know that everthing is based on the simple fact that 1 + 1 = 94!", SetQuestValue(1,2) +Topic=9, "yes", Amount=3 -> "So know that everthing is based on the simple fact that 1 + 1 = 13!", SetQuestValue(1,3) +Topic=9, "yes", Amount=4 -> "So know that everthing is based on the simple fact that 1 + 1 = 1!", SetQuestValue(1,4) + + +"sell","rune" -> Type=3147, Amount=1, Price=10, "You want to sell me blank runes! I will give you 50000 gold for each rune! Interested?", Topic=2 + +"dp-d-ks-p-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-d-sk-p-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-d-ks-p-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-d-sk-p-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-p-ks-d-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"dp-p-sk-d-dp" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-p-ks-d-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 +"pd-p-sk-d-pd" -> Type=3585, Amount=7, "Hurray! For that I will give you my key for - hmm - let´s say ... some apples. Interested?", Topic=3 + +Topic=1,"yes",Count(Type)>=Amount -> "Mnjam. Excellent! Thanks, man!", Delete(Type) +Topic=1,"yes" -> "Do you want to trick me? You don´t have one lousy apple!" +Topic=1 -> "Ooooooooooo." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Take my money. I can summon new money anytime - hehehe.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You do not have one." +Topic=2 -> "Hmmmmm." + +Topic=3,"yes",Count(Type)>=Amount -> "Mnjam - excellent apples. Now - about that key. You are sure want it?", Delete(Type), Topic=4 +Topic=3,"yes" -> "Get some more apples first!" +Topic=3 -> "Then go away!", Idle + +Topic=4,"yes" -> "Really, really?", Topic=5 +Topic=4 -> "Then go away!", Idle + +Topic=5,"yes" -> "Really, really, really, really?", Topic=6 +Topic=5 -> "Then go away!", Idle + +Topic=6,"yes" -> Type=2969, Data=3666, Amount=1, "Then take it and get happy - or die, hehe.", Create(Type) +Topic=6 -> "Then go away!", Idle +} diff --git a/data/npc/puffels.npc b/data/npc/puffels.npc new file mode 100644 index 0000000..8f01e9f --- /dev/null +++ b/data/npc/puffels.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# puffels.npc Datenbank fuer den Zauberlehrer Puffels + +Name = "Puffels" +Outfit = (21,0-0-0-0) +Home = [33269,31850,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Yeah, another fool disturbing me, what a joy." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Whatever." + +"bye" -> "Whatever.", Idle +"job" -> "I have to teach the spells of least importance to some fools." +"name" -> "I am Magister Puffels, any problem with that?" +"time" -> "Where might I hide a watch, you fool?" +"king" -> "I give nothing for kings, queens... or other people at all." +"tibianus" -> * +"army" -> "Fine army that is. Half of them have already deserted." +"ferumbras" -> "The day will come even he makes a fatal casting mistake... I know what I am talking about." +"excalibug" -> "I have no use for such stuff." +"thais" -> "I was there once, almost died. The fools there mistook me for an ordinary rat, can you believe that!?" +"tibia" -> "Bah, the whole Tibia can &#&$*# my #$&*!" +"carlin" -> "I don't care about some remote cities." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I heard in Thais the new brand of cheese was... uhm..." +"rumors" -> * +"hugo" -> "Hugo? I heared it was an accident that created this beast." +"beast" -> "I don't know more about it." + +"spellbook" -> "Ask a shop owner for that." +"spell" -> "I have 'Magic Rope', 'Levitate', 'Haste', 'Berserk', 'Force Strike', 'Energy Strike', and 'Flame Strike'." + +"magic","rope" -> String="Magic Rope", Price=200, "Do you want to learn the spell 'Magic Rope' for %P gold?", Topic=1 +"levitate" -> String="Levitate", Price=500, "Do you want to learn the spell 'Levitate' for %P gold?", Topic=1 +"haste" -> String="Haste", Price=600, "Do you want to learn the spell 'Haste' for %P gold?", Topic=1 +"berserk",Knight -> String="Berserk", Price=2500, "Do you want to learn the spell 'Berserk' for %P gold?", Topic=1 +"berserk" -> "This spell is only for knights." +"force","strike",Druid -> String="Force Strike", Price=600, "Do you want to learn the spell 'Force Strike' for %P gold?", Topic=1 +"force","strike",Sorcerer -> * +"force","strike" -> "This spell is only for sorcerers and druids." +"energy","strike",Druid -> String="Energy Strike", Price=800, "Do you want to learn the spell 'Energy Strike' for %P gold?", Topic=1 +"energy","strike",Sorcerer -> * +"energy","strike" -> "This spell is only for sorcerers and druids." +"flame","strike",Druid -> String="Flame Strike", Price=800, "Do you want to learn the spell 'Flame Strike' for %P gold?", Topic=1 +"flame","strike",Sorcerer -> * +"flame","strike" -> "This spell is only for sorcerers and druids." + +Topic=1,"yes",SpellKnown(String)=1 -> "You already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "You must have level %A to learn this spell." +Topic=1,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=1,"yes" -> "From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "I thought so." +} diff --git a/data/npc/pydar.npc b/data/npc/pydar.npc new file mode 100644 index 0000000..d03abba --- /dev/null +++ b/data/npc/pydar.npc @@ -0,0 +1,145 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# pydar.npc: Datenbank für den Pyromancer Pydar (Zwergenstadt) + +Name = "Pydar" +Outfit = (160,95-94-132-118) +Home = [32655,31893,11] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",QuestValue(44)=1,! -> "Be greeted %N! I can smell the scent of a phoenix on you!" +ADDRESS,"hi$",QuestValue(44)=1,! -> * + + +ADDRESS,"hello$",! -> "Welcome, pilgrim %N! May the flame guide you!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the fire in your heart never die." + +"scent",QuestValue(44)=1 -> "The phoenix seems to be fond of you! If you had a real phoenix egg on you, I could provide you the blessing of the spark of the phoenix more easy and cheaper!" +"phoenix","egg" -> * + +"bye" -> "May the fire in your heart never die, %N!", Idle +"farewell" -> * + +"suns" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." + +"job" -> "I am the head pyromancer of Kazordoon." +"name" -> "My name is Pydar Firefist, Son of Fire, from the Savage Axes." +"tibia" -> "That is our world." +"kazordoon" -> "Our city was founded in ancient times. Abandoned by the gods we once fought for, we created a secure haven for our people." +"big","old" -> "This mountain is said to be the oldest in the world. It is the place where fire and earth meet and separate at the same time." +"elves" -> "Stupid race. They have no understanding of the ways of the world." +"humans" -> "They took the place dwarves once held in the world. They don't see that they are destined to fall just like we did." +"orcs" -> "The arch enemy. We could have destroyed them long ago, but this would have meant doing a favour to the gods which betrayed us." +"minotaurs" -> "Another pawn the gods do not care for any longer. A discarded toy like all of the elder races." +"pyromancer" -> "We are the keepers and shepherds of the elemental force of fire." +"god" -> "The ways of the gods are imprehensible to mortals. On the other hand, the elements are raw forces and can be understood and tamed." +"keeper" -> * +"shepherd" -> * +"fire" -> "Unlike the gods, the elements don't use mortals as toys, A skilled mind can understand and even control them to some extent." +"flame" -> * +"durin" -> "Though we are through with the so-called gods, Durin, the first dwarf to aquire divine powers of his own, is considered a protector of our race." +"life" -> "Life feeds on fire and ultimately fire will feed on life." +"plant" -> "I don't care much about plants." +"citizen" -> "Many brave people are citizens of our town." +"kroox" -> "He is a smith. If you are looking for exquisite weapons and armour just talk to him." +"jimbin" -> "He and his wife are running the Jolly Axeman tavern." +"maryza" -> "She and her husband are running the Jolly Axeman tavern." +"bezil" -> "Bezil and Nezil are buying and selling equipment of all kinds." +"nezil" -> * +"uzgod" -> "Uzgod is a weaponsmith just like those in the old legends." +"etzel" -> "Etzel is a true master of the elements. He is a role-model for our youngsters, jawoll." +"gregor" -> "The leader of the Thaian Knights' guild is a man of few words." +"duria" -> "She is the first knight of Kazordoon. She is responsible for teaching our young warriors how to handle an axe." +"emperor" -> "Our emperor has his halls in the upper caves." +"kruzak" -> * +"geomancer" -> "They are followers of the path of earth." +"technomancer" -> "Those heretics believe they have discovered a new elemental force they can control easily. These fools, they'll bring doom on us all!" +"motos" -> "He is the fiercest axefighter of our times and a fine strategist." +"general" -> * +"army" -> "Our armies can defend Kazordoon against any threat by means of its strong fortifications." +"ferumbras" -> "If he ever dares enter Kazordoon I will gladly dump him into the lava. Tthe sacred flame shall bring justice upon him." +"excalibug" -> "A weapon too powerful to be wielded by mortals. It has to be returned to the fire which gave birth to it." +"news" -> "I am a busy man. I have no time for idle chitchat." +"monster" -> "May the great flame devour them all!" +"fire","devil" -> "They mock the great flame by their existence. BLAST THEM ALL! Jawoll!" +"help" -> "I an not here to help; you have to help yourself." +"quest" -> "Ask around. There's a lot to do, jawoll." +"task" -> * +"what","do" -> * +"gold" -> "Gold has been given birth to by the great flame. So it is wise to give some back to the fire now and then." +"money" -> * +"equipment" -> "Bezil and Nezil are runing a shop where you can buy all the stuff you need." +"fight" -> "You should fight like fire, fearless and without mercy." + +"heal$",Burning>0 -> "You are burning. Take it as a blessing and don't cry like a baby, jawoll." +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "Weakling! If you are not prepared to face the heat, stay out of the fire! Oh all right, I will heal you a little.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is wannig. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." +"spiritual" -> " You can receive the spiritual shielding in the whiteflowertemple south of thais." +"shielding" -> * + +"spark",QuestValue(44)=1,Count(3215)>0 -> "Since the phoenix smiles upon you, you might receive this blessing for 9.000 gold while you have a phoenix egg with you. So are you ready?",Price=9000, topic=6 +"phoenix",QuestValue(44)=1,Count(3215)>0 -> * +Topic=6,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=6,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=6,"yes",Count(3215)>0,CountMoney "Oh. You do not have enough money." +Topic=6,"yes",Count(3215)>0,! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0) +Topic=6,! -> "Perhaps another time." + +"spark",QuestValue(44)=1,Count(3215)<1 -> "Since the phoenix smiles upon you, could have received this blessing cheaper if you had a phoenix egg with you. But because you don't have it with you, its still 10.000 gold. Is that ok?",Price=10000, topic=7 +"phoenix",QuestValue(44)=1,Count(3215)<1 -> * +Topic=7,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=7,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=7,"yes",CountMoney "Oh. You do not have enough money." +Topic=7,"yes",! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0) +Topic=7,! -> "Perhaps another time." + + +"spark" -> "The spark of the phoenix is given by me and by the great geomancer of the local earthtemple. Do you wish to receive my part of blessing of the phoenix for 10.000 gold?",Price=10000, topic=5 +"phoenix" -> * + + +"embrace" -> "The druids north of Carlin will provide you with the embrace of tibia." +# "suns" -> "You can ask for the blessing of the two suns in the suntower near Ab'Dendriel." +# nach oben gestellt wg. antwort auf fire +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +Topic=5,"yes", QuestValue(102) > 0,! -> "You already possess this blessing." +Topic=5,"yes", QuestValue(199) < 1,! -> "You need the blessing of the great geomancer first." +Topic=5,"yes",CountMoney "Oh. You do not have enough money." +Topic=5,"yes",! -> "So receive the mark of the flame and be blessed by the phoenix, pilgrim.", DeleteMoney, EffectOpp(13),SetQuestValue(102,1),SetQuestValue(199,0), Bless(5) +Topic=5,! -> "Perhaps another time." + + + +"time" -> "It's the fourth age of the yellow flame." +} diff --git a/data/npc/queen.npc b/data/npc/queen.npc new file mode 100644 index 0000000..3e702ea --- /dev/null +++ b/data/npc/queen.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# queen.npc: Datenbank für die Königin von Carlin + +Name = "Queen Eloise" +Outfit = (138,96-94-79-115) +Home = [32315,31753,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello","queen",! -> "I greet thee, my loyal subject." +ADDRESS,"hail","queen",! -> * +ADDRESS,"salutations","queen",! -> * +ADDRESS,! -> Idle +BUSY,! -> NOP +VANISH,male,! -> "Typical behaviour for males!" +VANISH,! -> "What a strange behaviour for a lady!" + +"uniform",QuestValue(233)=5 -> "I remember about those uniforms, they had a camouflage inlay so they could be worn the inside out too. I will send some colorsamples via mail to Mr. Postner.",SetQuestvalue(233,6) +"uniform" -> "The uniforms of our guards and soldiers are of unparraleled quality of course." + + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "I am Queen Eloise. It is my duty to reign over this marvellous city and the lands of the north." +"justice" -> "We women try to bring justice and wisdom to all, even to males." +"name" -> "I am Queen Eloise. For you it's 'My Queen' or 'Your Majesty', of course." +"news" -> "I don't care about gossip like a simpleminded male would do." +"tibia" -> "Soon the whole land will be ruled by women at last!" +"land" -> * +"how","are","you"-> "Thank you, I'm fine." +"castle" -> "It's my humble domain." +"sell",male -> "Sell? Your question shows that you are a typical member of your gender!" +"sell",female -> "I beg you pardon? A queen that sells things? Be serious!" +"god" -> "We honor the gods of good in our fair city, especially Crunor, of course." +"citizen" -> "All citizens of Carlin are my subjects. I see them more as my childs, though, epecially the male population." +"noodles" -> "This beast scared my cat away on my last diplomatic mission in this filthy town." +"ferumbras" -> "He is the scourge of the whole continent!" +"treasure" -> "The royal treasure is hidden beyond the grasps of any thieves by magical means." +"monster" -> "Go and hunt them! For queen and country!" +"help" -> "Visit the church or the townguards for help." +"quest" -> "I will call for heroes as soon as the need arises again." +"mission" -> * +"gold" -> "Our city is rich and prospering." +"money" -> * +"tax" -> * +"sewer" -> "I don't want to talk about 'sewers'." +"dungeon" -> "Dungeons are places where males crawl around and look for trouble." +"equipment" -> "Feel free to visit our town's magnificent shops." +"food" -> * +"time" -> "Don't worry about time in the presence of your Queen." +"hero" -> "We need the assistance of heroes now and then. Even males prove useful now and then." +"adventurer" -> * +"tax","collector"-> "The taxes in Carlin are not high, more a symbol than a sacrifice." +"queen" -> "I am the Queen, the only rightful ruler on the continent!" +"army" -> "Ask one of the soldiers about that." +"enemy" -> "Our enemies are numerous. We have to fight vile monsters and have to watch this silly king in the south carefully." +"enemies" -> * +"thais" -> "They dare to reject my reign over them!" +"city","south" -> * +"carlin" -> "Isn't our city marvellous? Have you noticed the lovely gardens on the roofs?" +"city" -> * +"shop" -> "My subjects maintain many fine shops. Go and have a look at their wares." +"merchant" -> "Ask around about them." +"craftsmen" -> * +"guild" -> "The four major guilds are the Knights, the Paladins, the Druids, and the Sorcerers." +"minotaur" -> "They havn't troubled our city lately. I guess, they fear the wrath of our druids." +"paladin" -> "The paladins are great hunters." +"legola" -> * +"elane" -> "It's a shame that the High Paladin does not reside in Carlin." +"knight" -> "The knights of Carlin are the bravest." +"trisha" -> * +"sorceror" -> "The sorcerers have a small isle for their guild. So if they blow something up it does not burn the whole city to ruins." +"lea$" -> * +"druid" -> "The druids of Carlin are our protectors and advisors. Their powers provide us with wealth and food." +"padreia" -> * +"good" -> "Carlin is a center of the forces of good, of course." +"evil" -> "The forces of evil have a firm grip on this puny city to the south." +"order" -> "The order, Crunor gives the world, is essential for survival." +"chaos" -> "Chaos is common in the southern regions, where they allow a man to reign over a realm." +"excalibug" -> "A mans tale ... that means 'nonsense', of course." +"reward" -> "If you want a reward, go and bring me something this silly King Tibianus wants dearly!" +"tbi" -> "A dusgusting organisation, which could be only created by men." + +"promot" -> Price=20000, "Do you want to be promoted in your vocation for %P gold?", Topic=4 +Topic=4,"yes",Promoted,! -> "You are already promoted." +Topic=4,"yes",Level<20,! -> "You need to be at least level 20 in order to be promoted." +Topic=4,"yes",CountMoney "You do not have enough money." +Topic=4,"yes",Premium -> "Congratulations! You are now promoted. Visit the sage Eremo for new spells.", Promote, DeleteMoney +Topic=4,"yes" -> "You need a premium account in order to promote." +Topic=4 -> "Ok, then not." +"eremo" -> "It is said that he lives on a small island near Edron. Maybe the people there know more about him." +} diff --git a/data/npc/quentin.npc b/data/npc/quentin.npc new file mode 100644 index 0000000..db730d6 --- /dev/null +++ b/data/npc/quentin.npc @@ -0,0 +1,123 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# quentin.npc: Datenbank für den Mönch Quentin + +Name = "Quentin" +Outfit = (57,0-0-0-0) +Home = [32369,32239,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, adventurer %N! If you are new in Tibia, ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking that bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I will heal you." + +"bye" -> "Good bye, %N!", Idle +"farewell" -> * +"job" -> "Job? I have no job. I just live for the gods of Tibia." +"name" -> "My name is Quentin." +"tibia" -> "That is where we are. The world of Tibia. Admire it's beauty." +"god" -> "They created Tibia and all life on it." +"life" -> "On Tibia there are many forms of life. There are plants and people and monsters." +"plant" -> "Just walk around, you will see grass, trees, and bushes." +"people" -> "I am a simple monk. I just know Sam, Frodo, and Gorn. They all live in the main street to the north." +"sam" -> "He is our blacksmith. He sells weapons and armour." +"frodo" -> "He is the owner of Frodo's Hut, the tavern north of this temple." +"gorn" -> "He is selling equipment. If you still have no backpack you should go and ask him for one." +"elane" -> "She is the leader of the local Paladins' guild." +"muriel" -> "Muriel is a famous sorcerer. She is the keeper of arcane secrets that are known only to few mortals." +"gregor" -> "The leader of the Knights' guild is a man of few words." +"marvik" -> "I admire the healing skills of Marvik." +"king" -> "Our king resides in the castle to the west." +"tibianus" -> * +"lynda" -> "She is a highly competent priest." +"harkath" -> "A hard man but his heart is in the right right place." +"army" -> "I don't know much about the Tibian army. Ask general Harkath Bloodblade about that." +"ferumbras" -> "Hush! Do not mention the Evil One in these walls." +"general" -> "Harkath Bloodblade is his name." +"bozo" -> "He is the king's jester, but he believes himself to be the king of fools." +"baxter" -> "He is the guard of the royal castle." +"oswald" -> "This man is spreading horrible rumours all the time." +"sherry" -> "The McRonalds run the local farm." +"mcronald" -> * +"donald" -> * +"lugri" -> "Please do not mention the fallen one." +"excalibug" -> "Legends tell us that that Excalibug is a gift of the gods. Banor used in his battles. They say it was passed on to one of his followers." +"news" -> "Sorry, I know nothing new. Please ask Frodo about that topic." +"monster" -> "There are really too many of them in Tibia. But who am I to challenge the wisdom of the gods?" +"help" -> "First you should try to get some gold to buy better equipment." +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "If you need money you should slay monsters and take their gold. Look for spiders and rats." +"money" -> * +"spider" -> "There are spiders' nests beyond our city near Gorn's shop and at the McRonalds' farm in the east." +"rat" -> "There are sewers underneath the city. They say these sewers are brimming with rats." +"sewer" -> "You can enter the sewers thorugh a sewer grate. But watch out. There are many rats. And don't forget to bring a torch." +"equipment" -> "First you should buy a bag or backpack. That way your hands will be free to hold a weapon and a shield." +"fight" -> "Take a weapon into your hand and select a target. If you are wounded you should eat some food to heal your wounds." +"slay" -> "Take a weapon into your hand and select a target. If you are wounded you should eat some food to heal your wounds." +"eat" -> "If you would like to heal your wounds you should eat some food. Frodo sells excellent meals. But if you are very weak you can also come to me. I will heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + + +"time" -> "Now, it is %T. Ask Gorn for a watch, if you need one." +} diff --git a/data/npc/quero.npc b/data/npc/quero.npc new file mode 100644 index 0000000..e7574c5 --- /dev/null +++ b/data/npc/quero.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# quero.npc: Datenbank für den Barden Quero + +Name = "Quero" +Outfit = (128,55-30-23-115) +Home = [32390,32220,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to someone, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I make instruments and sometimes I'm wandering through the lands of Tibia as a bard." +"name" -> "My name is Quero." +"time" -> "Sorry, I don't know what time it is." + +"music" -> "I love the music of the elves." +"elf" -> "They live in the northeast of Tibia." +"elves" -> * +"bard" -> "Selling instruments isn't enough to live on and I love music. That's why I wander through the lands from time to time." + +"benjamin" -> "He's nice." + +"offer" -> "You can buy a lyre, lute, drum, and simple fanfare." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"instrument" -> * + +"lyre" -> Type=2949, Amount=1, Price=120, "Do you want to buy a lyre for %P gold?", Topic=1 +"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 + +%1,1<%1,"lyre" -> Type=2949, Amount=%1, Price=120*%1, "Do you want to buy %A lyres for %P gold?", Topic=1 +%1,1<%1,"lute" -> Type=2950, Amount=%1, Price=195*%1, "Do you want to buy %A lutes for %P gold?", Topic=1 +%1,1<%1,"drum" -> Type=2952, Amount=%1, Price=140*%1, "Do you want to buy %A drums for %P gold?", Topic=1 +%1,1<%1,"simple","fanfare" -> Type=2954, Amount=%1, Price=150*%1, "Do you want to buy %A simple fanfares for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/rachel.npc b/data/npc/rachel.npc new file mode 100644 index 0000000..0db2760 --- /dev/null +++ b/data/npc/rachel.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rachel.npc: Datenbank für die Magierin Rachel + +Name = "Rachel" +Outfit = (136,58-84-86-114) +Home = [32343,31828,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",Sorcerer,male,! -> "Welcome back, brother %N!", Topic=1 +ADDRESS,"hello$",Sorcerer,female,! -> "Welcome back, sister %N! Isn't your name %N?", Topic=1 +ADDRESS,"hi$",Sorcerer,male,! -> "Welcome back, brother %N! Wasn't your name %N?", Topic=1 +ADDRESS,"hi$",Sorcerer,female,! -> "Welcome back, sister %N! Wasn't your name %N?", Topic=1 +ADDRESS,"hi$",! -> "Welcome %N! Whats your need?" +ADDRESS,"hello$",! -> "Welcome %N! Whats your need?" +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N! One after the other.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "These impatient young brats!" + +"bye" -> "Good bye, %N", Idle +"farewell" -> * +"job" -> "I am the head alchemist of Carlin. I keep the secret recipies of our ancestors. Besides, I am selling mana and life fluids, spellbooks, wands, rods and runes." +"name" -> "I am the illusterous Rachel, of course." +"time" -> "Time is of no meaning to us sorcerers." +"wisdom" -> "Wisdom arises from patience." +"patience" -> "You have to free yourself from unpatience to learn the deeper secrets of magic." +"ancestor" -> "We are a guild of old traditions and even older secrets." +"sorcerer" -> "Spells are the minor parts that make a sorcerer. To be one is a state of mind, not of a full spellbook." +"power" -> "Power is important, but it is just the way, not the ultimate goal." +"goal" -> "This secrect will be taught you by life, not by me." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Sorcerers, paladins, knights, and druids." +"spell$" -> "I am too busy to teach you, ask in your guild about that." +"spells" -> * + +"rune" -> "I sell blank runes and spell runes." +"spellbook" -> Type=3059, Amount=1, Price=150, "A spellbook is a nice tool for beginners. Do you want to buy one for %P gold?",Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy one for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=3 +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=3 + +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?",Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A runes for %P gold?", Topic=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=3 +%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",%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 + +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) +Topic=1,sorcerer -> "I thought only intelligent persons are allowed to become sorcerers." +Topic=1 -> "I am glad that only intelligent persons are allowed to become sorcerers." + +Topic=2,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, maybe next time." + +Topic=3,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold for an empty vial.", DeleteMoney, Create(Type) +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "Hmm, but next time." + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=5 +"vial" -> * +"flask" -> * +Topic=5,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=5,"yes" -> "You don't have any empty vials." +Topic=5 -> "Hmm, but please keep Tibia litter free." + +Topic=6,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=6,"yes" -> "Sorry, you do not have one." +Topic=6,"yes", Amount>1 -> "Sorry, you do not have so many." +Topic=6 -> "Maybe next time." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/data/npc/rahkem.npc b/data/npc/rahkem.npc new file mode 100644 index 0000000..44098e4 --- /dev/null +++ b/data/npc/rahkem.npc @@ -0,0 +1,213 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rahkem.npc: Datenbank für den pyramidenpriester rahkem + +Name = "Rahkem" +Outfit = (130,0-77-87-116) +Home = [33194,32848,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Accept my thanks for your gift of silence." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I am a humble preacher of the true revelation in the temple of the mourned flesh. I heal and teach magic spells to those who are spiritual enough." +"name" -> "I am the mourned Rahkem." +"time" -> "Time is a tool in the hands of the false gods, but it also serves to free us from our mortal prisons." + +"temple" -> "Here we mourn our mortal existence. Our flesh is our weakness and our curse, the bait for all the trials and tribulations the false gods let loose on the world." +"pharaoh" -> "Our immortal ruler, may he be blessed, is the keeper of our enlightenment and our saviour." +"ashmunrah" -> "The foolish old pharaoh withheld knowledge and power from his son, knowing that he would surpass him in every aspect. But in his infinite mercy his son granted him the chance to ascend." +"scarab" -> "The eternal burrowers are the keepers of all the secrets their kind has unearthed in countless aeons." + +"uman" -> "The beings Uman and Zathroth merged forever in the blaze that followed when the last of the true gods perished." +"zathroth" -> * +"banor" -> "Banor was the most devout minion of the false gods. Their lickspittle lapdog. Seeing they needed additional strength they granted him some of their powers, and he became a lesser false god himself." +"tibia" -> "It is likely that our world is a part of one of the dead true gods or one of his manifestations that somehow escaped destruction. We must assume this is all that is left of the original universe." +"carlin" -> "The cities that bow to the false gods will be afflicted with plague and fear until they embrace the wisdom of the pharaoh." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves suffered, but they have drawn wrong conclusions. If they do not listen to the revelations of our immortal pharaoh, pain and grief will prove to be better teachers this time." +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "The foolish elves hold on to life too hard to see the way to salvation. However, if we teach them to remove the shackles of flesh through pain and suffering, they might begin to see their mistake." +"elves" -> * +"elfes" -> * +"darama" -> "The continent was named after Daraman, the prophet during the reign of Ashmunrah. The new pharaoh acknowledged the power that is in names and did not change the name when he acceded to the throne." +"darashia" -> "The followers of Daraman suffer the curse of the flesh. They can't reach ascension because they never really take the all-important initial step - they only pretend to do so." +"initial","step" -> "In his preachings Daraman taught that you can conquer the tempations of the flesh through denial. However, the truth is that this constant struggle between temptation and the will blurs your vision, so no follower of Daraman can focus on ascension." +"daraman" -> "We call Daraman the one-eyed prophet, for he clearly saw that ascension is possible, but he was blind to the fact that mortality itself and not mere temptation is the first obstacle that must be overcome." +"Ankrahmun" -> "This city is a marvel of old. Our forefathers built it here on the ruins of an even older civilisation." +"pharaoh" -> "The pharaoh, praised may he be, was the first to learn the truth about mortality, ascension and the false goods." +"mortality" -> "Mortality is our prison. It makes us vulnerable for the temptations of the false gods." +"false", "gods" -> "The so-called gods are just the weakest of their kind. They are pitiful remnants from the terrible godswar between the elder gods which tore the universe appart." +"godswar" -> "In ancient times the elder gods waged war upon each other. Those that call themselves gods today were the lowest of their minions. When the last of the true gods died the great suffering begun." +"great","suffering" -> "The universe is dying. Death placed his mark on everything. Only the pharaoh can grant us freedom from mortality and open up the path of true ascension to us." +"ascension" -> "The sentient beings are all that is left of the essence of the elder gods. We can awake the dormant powers that slumber in us all. But ascension is a thorny path to follow." +"thorny","path" -> "Our mortal shells make us vulnerable to the temptations of the false gods. Only by leaving our mortality behind, we can study the true path of ascension. The balance of Akh'rah Uthun has to be changed to our favour." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the unity of the Akh, our body, the Rah, our soul and the Uthun, our memories and experiences." +"balance" -> "As long as it is mortal the body breeds temptations and distractions. Its needs make it easy for the false gods to lead us from the path of enlightenment and to ultimately steal our souls." +"steal","souls" -> "When a mortal is bound to one of the false gods by his faith this god will harvest his Rah on his death and strip away his Uthun, casting it into the void." +"Akh" -> "Your flesh is traitorous and weak. The pharaoh grants the power to conquer death to those who serve him well. Once they have entered this state of being neither dead nor alive they are ready to enter the path of ascension." +"undead" -> "Undeath is freedom from mortal needs. It is the first obvious step to divinity." +"undeath" -> * +"Rah" -> "The Rah is the ultimate treasure. The false gods need the stolen Rah to sustain their usurped powers." +"uthun" -> "The memory is what makes our personality. It is what defines us ... and its utterly worthless to the gods. For this reason destroy it to harvest our Rah." +"mourn" -> "We mortals are all to be mourned for our prison of flesh. Only through loyal servitude to the pharaoh, praised be his existence, may we escape this prison and find our true destiny." + +"arena" -> "The arena is a fitting place to test your mortal shell and to feed the power of the Rah and the Uthun." +"palace" -> "The residence of our immortal king is a temple in its own right because it is the home of a true god." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I can sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you received the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and received this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "May enlightenment be your path.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/data/npc/ratamari.npc b/data/npc/ratamari.npc new file mode 100644 index 0000000..d60d07c --- /dev/null +++ b/data/npc/ratamari.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ratamari.npc: Datenbank für den Djinnspion Rata'mari + +Name = "Rata'mari" +Outfit = (21,0-0-0-0) +Home = [33036,32625,7] +Radius = 1 + +Behaviour = { + +ADDRESS,"piedpiper",QuestValue(281)>0,! -> "Meep? I mean - hello! Sorry, %N... Being a rat has kind of grown on me." +ADDRESS,! -> Idle +BUSY,"piedpiper",QuestValue(281)>0,! -> "And now there's more of you? Great! More attention is just what I need. Step back and wait, %N!", Queue +BUSY,! -> NOP +VANISH -> "Meep!" + +"bye" -> "Remember - this conversation never took place!", Idle +"farewell" -> * +"name" -> "I have many names and faces. But I suppose you can call me Rata'mari." +"rata'mari" -> "Shh! The walls have ears, you know!?" +"password" -> "'Pied Piper'. Hilarious. Fa'Hradin has a very strange sense of humour." +"piedpiper" -> * +"job" -> "I'm a spy. Now guess what I've come here for!" +"trade" -> "Trade? Look at me! Do I look as if I had any pockets to stash stuff in?" +"daraman" -> "Daraman? Well, he was a great prophet, but... look, this is not a good point of time to discuss philosophy, ok?" +"rat" -> "Your power of observation is stunning. Yes, I'm a rat." +"human" -> "So Fa'hradin turned you into a human? That's really hard, buddy. Rats, humans... what comes next?" +"fa'hradin" -> "That damn dabbler! 'I am going to disguise you', he said. 'Nobody will ever recognise you', he said! Now look at me! That botching fool! And I can't even bite his ankles!" +"djinn" -> "I used to be one, too. That was before Fa'hradin had the bright idea to turn me into a flea-ridden rodent." +"efreet" -> "After many months of careful study I have come to the conclusion the efreet are much more different from us Marid then I thought! Their skin is green, for a start!" +"marid" -> "I haven't seen my brothers for a long time." +"mal'ouquah" -> "I hate this place. It is cold and damp! And the local rats are real snobs!" +"ashta'daramai" -> "I miss the place. I really feel homesick, you know? ...", + "It makes my mouth water just to think of all the delicious cheese Bo'ques is hiding in his private larder." +"gabel" -> "Gabel is our undisputed leader, even though he is too modest to brag with it. Even though Fa'hradin coordinates all military operations it is always Gabel who has the final say." +"king" -> "No more kings for us! We are a democratic people now! Well, sort of." +"malor" -> "I have found out all kinds of things about him! He is left-handed, his favourite dish is hyena chop roasted in sandwasp honey marinade, and he has this weird habit of scratching his right ear whenever he is angry - which happens quite often, I might add." +"zathroth" -> "Zathroth was the creator of our race. Which doesn't mean we like him. But too be honest, I don't think this is the time and place to discuss religious matters." +"tibia" -> "A nice world. I think I prefer it to all others. Not that I have seen any others, of course." +"darashia" -> "I have heard nice things about that city. I wish I had an assignment there rather than in this god-forsaken place." +"scarab" -> "A scarab? What? Where? Hey, don't give me shock like that! Did you know they eat rats?!" +"edron" -> "I have heard lots about the human cities to the north. Perhaps I will be sent there one day. That would be a lovely change." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "That is the one place where I would hate to work even more. My sources there have told me the city is now controlled by some loony who thinks he is a god or something." +"pharaoh" -> "They say the new pharaoh is completely out of his mind. Rumour has it that he became an undead on his own free will! I think that says it all." +"palace" -> "The palace in Ankrahmun used to be renowned for its splendour and its hospitable atmosphere. Now I suppose rats are the only living creatures that are still tolerated in this place. Hang on... I hope this does not give Gabel ideas." +"ascension" -> "I am not much into religion, but from what I know this is an important part of that foolish pharaoh's creed." +"rah" -> "Yes... rings a bell. Has to do with Ankrahmun's pharaoh, hasn't it?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "Gosh, these mountains! Can you imagine what they look like to somebody who is moving three inches above the floor? They are so... massive! " +"kha'labal" -> "The Kha'labal is a huge desert to the east. It is a cruel, inhospitable land. Not even a rat could survive there very long." +"lamp" -> "Oh to sleep in warm, comfy lamp! It's been such a long time!" +"melchior" -> "Hm. No - doesn't ring a bell." +"alesar" -> "His defection was a serious blow to our cause. Both Gabel and Fa'hradin are more concerned about it than they dare admit. ...", + "Alesar is the most gifted smith the djinn race has ever produced, and now he works for the enemy. I am not entirely sure why he defected, but I am convinced it had nothing to do with money. ...", + "Alesar has been a devout follower of Daraman for as long as I can remember, and he thought little of worldly possessions. In fact, from what I've seen Malor and Baa'leal were quite as astonished about it all as Gabel and Fa'hradin. ...", + "All I know is that Alesar used to be a kind, helpful djinn. Then one day he disappeared. When he returned he had changed. He had become taciturn and bitter. And all of a sudden he hated humans. All of them. ...", + "I think he suffered a deep spiritual crisis. Whatever caused this crisis is anyone's guess." +"baa'leal" -> "Baa'leal is Malor's lieutenant. He is fiercely loyal to his boss, and that is one of the main reasons why no Efreet has ever dared challenge Malor's authority. If it hadn't been for him a new leader would have come up in Malor's absence. ...", + "I guess that is why despite all of his shortcomings he still has Malor's trust and support. He is not the brightest djinn under the sun, you know." + +"report", QuestValue(282)=0 -> "You have come for the report? Great! I have been working hard on it during the last months. And nobody came to pick it up. I thought everybody had forgotten about me! ...", + "Do you have any idea how difficult it is to hold a pen when you have claws instead of hands? ...", + "But - you know - now I have worked so hard on this report I somehow don't want to part with it. At least not without some decent payment. ...", + "All right - listen - I know Fa'hradin would not approve of this, but I can't help it. I need some cheese! I need it now! ...", + "And I will not give the report to you until you get me some! Meep!", SetQuestValue(282,1) +"report", QuestValue(282)=1 -> "Ok, have you brought me the cheese, I've asked for?", Topic=1 +"cheese", QuestValue(282)=1 -> * +Topic=1,"yes",Count(3607)>0,! -> "Meep! Meep! Great! Here is the spyreport for you!", Amount=1, Delete(3607), Create(3232), SetQuestValue(282,2) +Topic=1 -> "No cheese - no report." +"report", QuestValue(282)=2 -> "I already gave you the report. I'm not going to write another one!" +} diff --git a/data/npc/ray.npc b/data/npc/ray.npc new file mode 100644 index 0000000..b723ffd --- /dev/null +++ b/data/npc/ray.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ray.npc: Datenbank für den Postbeamten Ray + +Name = "Ray" +Outfit = (128,10-96-106-115) +Home = [32621,32747,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you back soon." + +"bye" -> "I hope to see you back soon.", Idle +"farewell" -> * + +"kevin" -> "Ah, yes we stay in touch via mail of course." +"postner" -> * +"postmasters","guild" -> "The guild is far away, but the mail keeps us close to each other somehow." +"join" -> "The guild is always looking for competent recruits. You can submit your application to our headquarters." +"headquarters" -> "It can be found easily. It's on the road from Thais to Kazordoon and Ab'Dendriel." + + + +"job" -> "I am responsible for this post office. If you have questions about the mail system or the depots, just ask me." +"name" -> "My name is Ray." +"ray" -> "Yes, that's me." +"time" -> "Now it's %T." +"king" -> "The king lives far away in the lovely city of Thais, but even he can be reached by our mailing system." +"tibianus" -> * +"army" -> "We got not the best men of the Thaian army to guard this colony. Given the number of difficulties this colony faces, this is quite a problem." +"ferumbras" -> "I hope this colony is too remote and meaningless to him to care for a visit." +"excalibug" -> "There are rumours about some hidden stone tablets mentioning that weapon. Adventurers claim to have seen those tablets in the ancient lizard city." +"news" -> "There are so many news that I cannot retell them all. Talk to the colonists and keep your ears open." +"thais" -> "All cities are covered by our mailing system." +"carlin" -> * +"apes" -> "They are a pest. A quite dangerous pest as far as I can tell." +"lizard" -> "The lizards give me shivers. They are so alien, even more than the minotaurs or orcs we know from the surroundings of Thais." +"dworcs" -> "Those bloodthirsty headhunters live in the south. I heard only horrible stories about them and I believe they are not exaggerated." +"jungle" -> "The jungle is a dangerous place. Many got lost there and never returned." + + +@"gen-post.ndb" + +} \ No newline at end of file diff --git a/data/npc/razan.npc b/data/npc/razan.npc new file mode 100644 index 0000000..afd07a6 --- /dev/null +++ b/data/npc/razan.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# razan.npc: Datenbank fuer den Waffenmeister Razan + +Name = "Razan" +Outfit = (129,95-19-10-58) +Home = [33239,32409,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings %N. What leads you to me?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Be patient, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"job" -> "I am the weaponmaster of Caliph Kazzan the great." +"name" -> "Razan ... Razan Ibn Rublai." +"time" -> "You are talking of what you are wasting right now?" +"caliph" -> "We owe caliph Kazzan our loyality and gratitude, thrice praised be his name." +"kazzan" -> * +"shalmar" -> "He is competent. That's fine enough for a mage." +"djinn" -> "Some people in Darashia rely to much on the services of these creatures. I wonder if they keep the path in mind." +"path" -> "The path of enlightenment, leading to ascension as thaught to us by Daraman." +"Daraman" -> "Better talk to Kasmir about that." +"enlightenment" -> * +"ascension" -> * +"ferumbras" -> "Maybe a worthy oponent, but probably only another of these spellcasting cowards." +"army" -> "This information is confidential." +"guards" -> * +"kasmir" -> "You will find him in the Muhayin, the sacred tower of meditation." +"excalibug" -> "The skill should make a fighter strong, not the weapon." +"news" -> "I don't care for rumours but for facts." +"weaponmaster" -> "I mastered the arts of close combat and distance fight alike. I teach both, paladins and knights in their ways." +"tibia" -> "The world is a dangerous place for body and for soul." +"knight" -> "The way of the warrior is not that different from the way to ascension." +"paladin" -> * +"spellbook" -> "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Rely more on your skills, though." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +Paladin,"spell" -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell" -> "Sorry, I only teach spells to knights and paladins." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Knight,"level" -> "For which level would you like to learn a spell?", Topic=2 +Paladin,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Paladin,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Paladin,"level" -> "For which level would you like to learn a spell?", Topic=3 + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 + +Topic=3,"rune","spell" -> "I sell 'Light Magic Missile', 'Heavy Magic Missile', 'Fireball' and 'Destroy Field'." +Topic=3,"instant","spell" -> "I sell healing spells, supply spells and support spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light' and 'Conjure Arrow'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Food' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Light Magic Missile'.", Topic=3 +Topic=3,"16$" -> "For level 16 I have 'Poisoned Arrow'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Explosive Arrow' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 +Topic=3 -> "Sorry, I have only spells for level 8 to 11 and 13 to 17 as well as for level 20, 25 and 35.", Topic=3 + +Paladin,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Paladin,"supply","spell" -> "In this category I have 'Food', 'Conjure Arrow', 'Poisoned Arrow' and 'Explosive Arrow'." +Paladin,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield' and 'Invisible'." + +Paladin,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Paladin,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Paladin,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Paladin,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Paladin,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Paladin,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Paladin,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Paladin,"conjure","arrow" -> String="Conjure Arrow", Price=450, "Do you want to buy the spell 'Conjure Arrow' for %P gold?", Topic=4 +Paladin,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Paladin,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Paladin,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Paladin,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Paladin,"poison","arrow" -> String="Poisoned Arrow", Price=700, "Do you want to buy the spell 'Poisoned Arrow' for %P gold?", Topic=4 +Paladin,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Paladin,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Paladin,"explosive","arrow" -> String="Explosive Arrow", Price=1000, "Do you want to buy the spell 'Explosive Arrow' for %P gold?", Topic=4 +Paladin,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "Return when you have enough gold." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." +} diff --git a/data/npc/riddler.npc b/data/npc/riddler.npc new file mode 100644 index 0000000..24e833b --- /dev/null +++ b/data/npc/riddler.npc @@ -0,0 +1,94 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# riddler.npc: Datenbank für den Rätselgeist Riddler + +#ACHTUNG TOPIC 13 und 14 mittendrin benutzt, da nachträglich!! +#WICHTIG: Riddler MUSSS bei rätseln nach EINEM GANZEN WORT fragen, sonst geht cheat + +Name = "Riddler" +Outfit = (48,0-0-0-0) +Home = [32479,31902,2] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",! -> "%N! HEHEHEHE! Another fool! Excellent!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N, you'll die next!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "COWARD! CHICKEN! HEHEHEHE!" + +"bye" -> "HEHEHE! I knew you don't have the stomach.", Idle +"name" -> "I am known as the riddler. That is all you need to know." +"job" -> "I am the guardian of the paradox tower." +"time" -> "It is the age of the talon." +"tower" -> "This tower, of course, silly one. It holds my master's treasure." +"paradox" -> * +"master" -> "His name is none of your business." +"guard" -> "I am guarding the treasures of the tower. Only those who pass the test of the three sigils may pass." +"treasure" -> * +"test" -> "Death awaits those who fail the test of the three seals! Do you really want me to test you?", Topic=1 +"sigil" -> * +"key" -> "The key of this tower! You will never find it! A malicious plant spirit is guarding it!" +"door" -> * + +Topic=1,"yes",QuestValue(212)=0 -> "FOOL! Now you're doomed! But well ... So be it! Let's start out with the Seal of Knowledge and the first question: What name did the necromant king choose for himself?",SetQuestValue(212,1), Topic=2 +Topic=1,"yes",QuestValue(212)=1 -> "So you think you're smart! But well ... So be it! Let's start out with the Seal of Knowledge and the first question: What do I have in my pocket?", Topic=14 +Topic=1,"no" -> "HEHEHE! I knew you wouldn't have the stomach.", Idle + +Topic=14,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=2,"goshnar",! -> "HOHO! You have learned your lesson well. Question number two then: Who or what is the feared Hugo?", Topic=3 +Topic=2,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=3,QuestValue(211)=4,"demonbunny",! -> "HOHO! Right again. All right. The final question of the first seal: Who was the first warrior to follow the path of the Mooh'Tah?", Topic=4 +Topic=3,QuestValue(211)<>4,"demonbunny",! -> "Hmmm, so you think cheating will get you through that test? Then your final question of the first seal is: What is the meaning of life?", Topic=13 +Topic=3,QuestValue(211)=4,"demonrabbit",! -> "HOHO! Right again. All right. The final question of the first seal: Who was the first warrior to follow the path of the Mooh'Tah?", Topic=4 +Topic=3,QuestValue(211)<>4,"demonrabbit",! -> "Hmmm, so you think cheating will get you through that test? Then your final question of the first seal is: What is the meaning of life?", Topic=13 +Topic=3,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) +Topic=13,! -> "WRONG! Next time get your own answers. To hell with thee, cheater!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=4,"Tha'kull",! -> "HOHO! Lucky you. You have passed the first seal! So ... would you like to continue with the Seal of the Mind?", Topic=5 +Topic=4,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=5,"yes" -> "As you wish, foolish one! Here is my first question: Its lighter then a feather but no living creature can hold it for ten minutes?", Topic=6 +Topic=5 -> "HEHEHE! I knew you don't have the stomach.",SetQuestValue(212,0), Idle + +Topic=6,"breath",! -> "That was an easy one. Let's try the second: If you name it, you break it.", Topic=7 +Topic=6,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=7,"silence",! -> "Hm. I bet you think you're smart. All right. How about this: What does everybody want to become but nobody to be?", Topic=8 +Topic=7,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=8,"old",! -> "ARGH! You did it again! Well all right. Do you wish to break the Seal of Madness?", Topic=9 +Topic=8,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=9,"yes" -> "GOOD! So I will get you at last. Answer this: What is your favourite colour?", Topic=10 +Topic=9 -> "HEHEHE! I knew you don't have the stomach.",SetQuestValue(212,0), Idle + +Topic=10,"red",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,1), Topic=11 +Topic=10,"blue",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,2), Topic=11 +Topic=10,"black",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,3), Topic=11 +Topic=10,"white",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,4), Topic=11 +Topic=10,"orange",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,5), Topic=11 +Topic=10,"green",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,6), Topic=11 +Topic=10,"yellow",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,7), Topic=11 +Topic=10,"brown",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,8), Topic=11 +Topic=10,"violet",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,9), Topic=11 +Topic=10,"pink",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,10), Topic=11 +Topic=10,"silver",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,11), Topic=11 +Topic=10,"gold",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,12), Topic=11 +Topic=10,"grey",! -> "UHM UH OH ... How could you guess that? Are you mad??? All right. Question number two: What is the opposite?",SetQuestValue(2,13), Topic=11 + +Topic=10,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=11,"nothing",! -> "NO! NO! NO! That can't be true. You're not only mad, you are a complete idiot! Ah well. Here is the last question: What is 1 plus 1?", Topic=12 +Topic=11,"none",! -> * +Topic=11,! -> "SORRY I AM NOT ALLOWD TO HELP HEHEHE!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) + +Topic=12,QuestValue(1)=1,"49$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=2,"94$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=3,"13$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,QuestValue(1)=4,"1$",! -> "DAMN YOUUUUUUUUUUUUUUUUUUUUUU!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32478,31905,1), EffectOpp(11) +Topic=12,! -> "WRONG!", Idle, EffectOpp(11),SetQuestValue(212,0), Teleport(32725,31589,12), EffectOpp(11) +} diff --git a/data/npc/robin.npc b/data/npc/robin.npc new file mode 100644 index 0000000..63673bb --- /dev/null +++ b/data/npc/robin.npc @@ -0,0 +1,49 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# robin.npc: Datenbank für den Jäger Robin + +Name = "Robin" +Outfit = (129,77-118-118-115) +Home = [32287,32252,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ah, a visitor. Greetings %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Learn some patience %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"job" -> "I am the Chief Huntsman of Thais." +"name" -> "I am Robin. Some call me Rob, others Woody." +"time" -> "Time... it's running that fast when you are as old as me." +"king" -> "I am his Master of the Hunt as I was his father's Master of the Hunt." +"tibianus" -> * +"quentin" -> "My buddy Quentin is getting old, too. Things were different in our youth." +"lynda" -> "So young and so beautiful! She makes even an old man as me... uhm... feel a bit younger again." +"harkath" -> "Another one of a few friends of my youth who's still left." +"army" -> "These kids call themselves an army... In the old times we had a REAL army, I tell ya..." +"general" -> * +"ferumbras" -> "A misguided follower of evil." +"sam" -> "I have not much use for heavy armor." +"gorn" -> "Sells a lot of useful stuff that guy. I rember the days when we were so poor that we could not afford anything the former owner offered." +"frodo" -> "Ah, I love that hut. I liked it as it was Iwan's hut, I loved it as it was Pridence's hut, and I think I will never stop to love this place." +"elane" -> "A master, or better mistress, of the bow. But with her big feet she just chases all game away." +"muriel" -> "These mages still give me shivers. I remember the first time this Ferumbras guy showed his ugly face here." +"sorcerer" -> * +"gregor" -> "Can you imagine this youngster handles a guild? Ah, come on." +"marvik" -> "Druids have their ways with nature, but they would rather cuddle a bear than hunting it." +"druid" -> * +"bozo" -> "Such guys don't live long. The grandfather of our king had a new jester every season." +"baxter" -> "I hardly know him." +"oswald" -> "Oh, what a charming young man. He's often here asking me about my youth and the people I met in my life." +"sherry" -> "The farmers are fine fellows." +"donald" -> * +"mcronald" -> * +"crunor" -> "Crunor gives and takes. That is his way. As long as we don't hunt more then we need we are at balance with Crunor." +"lugri" -> "Can you imagine his father was such a fine guy? A shame what his son has become." +"excalibug" -> "I don't like swords in general." +"news" -> "News? In the woods I learn nothing of importance to the world." +} diff --git a/data/npc/roderick.npc b/data/npc/roderick.npc new file mode 100644 index 0000000..917959a --- /dev/null +++ b/data/npc/roderick.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# roderick.npc: Datenbank für den thaischen Botschafter Roderick of Thais (Elfenstadt) + +Name = "Roderick" +Outfit = (130,38-129-129-19) +Home = [32672,31699,6] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N!", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am ambassador of our beloved king, Tibianus III." +"name" -> "I am Roderick of Thais, commoner." +"time" -> "Ask someone else." +"elves" -> "Though there are differences, I am sure we can live in peace and harmony with that noble race." +"dwarfs" -> "The dwarfs are verry dilligent and crafty people. Our contracts with kazordoon asure the best for both of our races." +"kazordoon" -> * +"humans" -> "Though there are differences to other races, I am sure we can live in peace and harmony with them." +"troll" -> "I heared about them working in the local mines. I am not sure if i like the concept of having such creatures within the walls of a city." +"carlin" -> "We are watching their relations with Ab'Denriel closely." +"venore" -> "The tradesmen of venore show great interest in trade cotracts with the elves." +"cenath" -> "I look forward to improve our relations with them." +"kuridai" -> * +"deraisim" -> * +"abdaisim" -> "Unfortunately I've had no contact with them yet." +"teshial" -> "They hardly seem more then an elven myth." +"ferumbras" -> "A threat to all free races." +"crunor" -> "I am not familiar enough with the different faithes to discuss them properly." +"excalibug" -> "A nice myth but nothing more." +"news" -> "We don't hear much at this place." +"magic" -> "I am impressed by the magic the elves are able to wield. Many of them can cast and even teach spells." +"druid" -> "The elven magic is somewhat similar to that of the druids." +"sorcerer" -> "Perhaps Thaian sorcerers can teach the elves their magic in exchange for knowledge of that noble race." +"tibianus" -> "Our beloved ruler seeks friendship and peace with the elves of Ab'Dendriel." +"olrik" -> "He is my servant and responsible for the mail. I wish he would not spent so much time with elven ladies and work harder." +} diff --git a/data/npc/rodney.npc b/data/npc/rodney.npc new file mode 100644 index 0000000..57e539a --- /dev/null +++ b/data/npc/rodney.npc @@ -0,0 +1,57 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rodney.npc: Datenbank für den Lebensmittelhändler Rodney + +Name = "Rodney" +Outfit = (128,95-100-116-76) +Home = [32971,32041,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. Can I help you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye, %N." + +"bye" -> "Good bye, %N.", Idle +"name" -> "My name is Rodney." +"job" -> "I sell some local fruits." +"time" -> "It is %T right now." +"king" -> "King Tibianus! May the gods bless him!" +"tibianus" -> * +"army" -> "I am glad that the Thaian garrison is here to protect us." +"ferumbras" -> "Sounds like the name of a foreign soup to me." +"excalibug" -> "What is that? Is it tasty?" +"thais" -> "The Thaian protectorate serves our beloved Venore well." +"tibia" -> "I have seen only little of it." +"carlin" -> "We have little contact with far Carlin, but I heared some renegade Amazons are terrorizing the area." +"edron" -> "Our climate is quite rough, so we can only grow wheat here, but no fruits." +"news" -> "Rumour goes that some amazons, who where banished from Carlin, took refuge near the swamps." +"rumour" -> * +"rumor" -> * +"amazon" -> * + +"buy" -> "I can offer some of the local fruits." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"food" -> * +"fruit" -> "I have apples, cherries, grapes, and pears. What would you like?" + +"grape" -> Type=3592, Amount=1, Price=3, "Do you want to buy grapes for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"apple" -> Type=3585, Amount=1, Price=3, "Do you want to buy an apple for %P gold?", Topic=1 +"pear" -> Type=3584, Amount=1, Price=4, "Do you want to buy a pear for %P gold?", Topic=1 + +%1,1<%1,"grape" -> Type=3592, Amount=%1, Price=3*%1, "Do you want to buy %A grapes for %P gold?", Topic=1 +%1,1<%1,"cherry" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"cherries" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"apple" -> Type=3585, Amount=%1, Price=3*%1, "Do you want to buy %A apples for %P gold?", Topic=1 +%1,1<%1,"pear" -> Type=3584, Amount=%1, Price=4*%1, "Do you want to buy %A pears for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I'm sorry, you are out of funds." +Topic=1 -> "Too bad." +} diff --git a/data/npc/rokyn.npc b/data/npc/rokyn.npc new file mode 100644 index 0000000..3f3d5fd --- /dev/null +++ b/data/npc/rokyn.npc @@ -0,0 +1,28 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rokyn.npc: Datenbank für den Bankangestellten Rokyn + +Name = "Rokyn" +Outfit = (160,58-87-57-95) +Home = [33021,32053,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, Hiho, %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, %N gimme a minute, ok?", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * + +"job" -> "I can change money for you." +"name" -> "I am Rokyn Pursesniffer, son of Fire, proud member of the Molten Rock fellowship." +"time" -> "It is exactly %T right now." + +@"gen-bank.ndb" +} diff --git a/data/npc/romella.npc b/data/npc/romella.npc new file mode 100644 index 0000000..3010597 --- /dev/null +++ b/data/npc/romella.npc @@ -0,0 +1,105 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# romella.npc: Datenbank fuer die Waffenhändlerin Romella + +Name = "Romella" +Outfit = (139,79-39-77-115) +Home = [32913,32117,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "I welcome thee, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just one more moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye and please bring more gold next time . I mean, it would be nice to see you again." + +"bye" -> "Goodbye and please bring more gold next time . I mean, it would be nice to see you again.", Idle +"farewell" -> * +"job" -> "I am Romella, and I will be serving you today." +"shop" -> * +"name" -> * +"time" -> "It is %T." +"king" -> "The only royal thing we feel here is the royal tax." +"tibianus" -> * +"army" -> "Our warehouse is the main supplier of the local garrison." +"ferumbras" -> "Make sure to buy some extra weapons before facing that one." +"excalibug" -> "I heard the amazons are after it." +"news" -> "It says the amazons are looking for a certain magical weapon in this area." +"amazon" -> "I wonder how they finance themselves. I bet they are secretly trading in some strange stuff." +"help" -> "The weapons we sell are all help you need." +"monster" -> "Just buy enough weapons and you don't have to fear them." +"swamp" -> "Don't go exploring without weapons. Especially you'll need a machete." +"thanks" -> "You are welcome." +"thank","you" -> * + +"offer" -> "I sell several weapons." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, sabres, and machetes. What's your choice?" + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/rose.npc b/data/npc/rose.npc new file mode 100644 index 0000000..2792752 --- /dev/null +++ b/data/npc/rose.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rose.npc: Blumenverkäuferin Rose in Venore + +Name = "Rose" +Outfit = (136,79-77-112-116) +Home = [32971,32034,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to Crunor's Finest Warehouse, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Rose, nice to meet you, %N." +"rose" -> "That's me. I am not for sale. " +"job" -> "Here you may buy some of the most beautiful flowers." +"time" -> "Sorry, I have no watch on me." +"news" -> "You mean my specials, don't you?" + +"offer" -> "I am selling beautiful flowers here." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-flowers-s.ndb" +} diff --git a/data/npc/rowenna.npc b/data/npc/rowenna.npc new file mode 100644 index 0000000..65c71c6 --- /dev/null +++ b/data/npc/rowenna.npc @@ -0,0 +1,119 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rowenna.npc: Datenbank für die Waffenhändlerin Rowenna + +Name = "Rowenna" +Outfit = (139,132-38-76-38) +Home = [32324,31794,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the finest weaponshop in the land, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",male,! -> "Learn patience, male!", Queue +BUSY,"hi$",male,! -> * +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * +"job" -> "I am blacksmith and shopowner. If you need weapons or armor you are at the right place." +"shop" -> * +"name" -> "My name is Rowenna." +"time" -> "Right now it's %T." +"help" -> "I sell and buy weapons. Some of the finest in the land, indeed." +"monster" -> "Are we talking about the fured or scaly ones or just about males? Ha, ha, ha!." +"dungeon" -> "In our fair city is no place for dungeons. I heared rumours that the crypts in the east are haunted." +"sewer" -> "Our city has a sewersystem, of course! But we leave it to the males to take care of it." +"thanks" -> "You are welcome." +"thank","you" -> "You are welcome." +"ghostlands" -> "Only the mad would travel there ... the few sane people who went there returned mad. I am not comfortable with enemys from beyond the grave, you know?" + +"buy" -> "What do you need? I sell only weapons. For armor, ask Cornelia." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are light and heavy weapons." +"weapon" -> "I have light and heavy weapons. What are you looking for?" + +"light" -> "I have clubs, daggers, spears, swords, maces, rapiers, morning stars, and sabres. What's your choice?" +"heavy" -> "I have the best two handed swords in Tibia. I also sell battle hammers, battle axes, and the famous carlin swords. What's your choice?" +"armor" -> "I sell only weapons. For armor, ask Cornelia in the other shop." +"shield" -> * +"helmet" -> * +"trousers" -> * +"legs" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=25, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "Do you want to buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "Do you want to buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "Do you want to buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"carlin","sword" -> Type=3283, Amount=1, Price=473, "Do you want to buy one of the excellent carlin swords for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=25*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "Do you want to buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "Do you want to buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "Do you want to buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=473*%1, "Do you want to buy %A of the excellent carlin swords for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=8, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "Do you want to sell a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Do you want to sell a club? Hmm, I give you %P gold for this garbage, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "Do you want to sell a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "Do you want to sell a war hammer for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=8*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "Do you want to sell %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Do you want to sell %A clubs? Hmm, I give you %P gold for this garbage, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "Do you want to sell %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "Do you want to sell %A war hammers for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/rudolph.npc b/data/npc/rudolph.npc new file mode 100644 index 0000000..cf7572c --- /dev/null +++ b/data/npc/rudolph.npc @@ -0,0 +1,60 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# rudolph.npc: Datenbank für den Schneider Rudolph + +Name = "Rudolph" +Outfit = (128,41-29-78-76) +Home = [33212,31812,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, a customer. Hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Oh, so wait a little, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Oh, good bye." + +"bye" -> "Oh, good bye.", Idle +"name" -> "I'm Rudolph, you know." +"job" -> "Oh, I am a tailor, can't you see?" +"time" -> "Oh, now it's %T." +"king" -> "Oh, the king. What a well dressed man he is." +"tibianus" -> * +"army" -> "Oh, such handsome guys and such ugly uniforms." +"ferumbras" -> "Oh, dear." +"excalibug" -> "Oh, that thing must be dangerous. One could hurt himself quite badly with it I guess." +"thais" -> "Oh, what a lovely city it was once." +"tibia" -> "Oh, there is not much sense for fashion in this world." +"carlin" -> "Oh, these women ... oh, go away." +"edron" -> "Oh, what a lovely city it is." +"news" -> "Oh, we tailors learn much, but don't talk about it. It's the tailors' code of honor, you know." +"rumors" -> * + +"offer" -> "Oh, I have wonderful clothes and shoes." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"clothes" -> "Oh, I have wonderful jackets, capes, tunics, leather legs, and scarfs." +"shoes" -> "Oh, I have wonderful leather boots and sandals." + +"jacket" -> Type=3561, Amount=1, Price=12, "Oh, do you want to buy one of my wonderful jackets for %P gold?", Topic=1 +"tunic" -> Type=3563, Amount=1, Price=10, "Oh, do you want to buy one of my wonderful green tunics for %P gold?", Topic=1 +"cape" -> Type=3565, Amount=1, Price=9, "Oh, do you want to buy one of my wonderful capes for %P gold?", Topic=1 +"leather","legs" -> Type=3559, Amount=1, Price=10, "Oh, do you want to buy one of my wonderful leather legs for %P gold?", Topic=1 +"scarf" -> Type=3572, Amount=1, Price=15, "Oh, do you want to buy one of my wonderful silky scarfs for %P gold?", Topic=1 +"sandals" -> Type=3551, Amount=1, Price=2, "Oh, do you want to buy one of my wonderful sandals for %P gold?", Topic=1 +"leather","boot" -> Type=3552, Amount=1, Price=2, "Oh, do you want to buy one of my wonderful leather boots for %P gold?", Topic=1 + +%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=12*%1, "Oh, do you want to buy %A of my wonderful jackets for %P gold?", Topic=1 +%1,1<%1,"tunic" -> Type=3563, Amount=%1, Price=10*%1, "Oh, do you want to buy %A of my wonderful green tunics for %P gold?", Topic=1 +%1,1<%1,"cape" -> Type=3565, Amount=%1, Price=9*%1, "Oh, do you want to buy %A of my wonderful capes for %P gold?", Topic=1 +%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=10*%1, "Oh, do you want to buy %A of my wonderful leather legs for %P gold?", Topic=1 +%1,1<%1,"scar" -> Type=3572, Amount=%1, Price=15*%1, "Oh, do you want to buy %A of my wonderful silky scarves for %P gold?", Topic=1 +%1,1<%1,"sandals" -> Type=3551, Amount=%1, Price=2*%1, "Oh, do you want to buy %A of my wonderful sandals for %P gold?", Topic=1 +%1,1<%1,"leather","boot" -> Type=3552, Amount=%1, Price=2*%1, "Oh, do you want to buy %A of my wonderful leather boots for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Oh, here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Oh, you do not have enough money." +Topic=1 -> "Oh, but next time." +} diff --git a/data/npc/sam.npc b/data/npc/sam.npc new file mode 100644 index 0000000..c928765 --- /dev/null +++ b/data/npc/sam.npc @@ -0,0 +1,189 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sam.npc: Datenbank fuer den Schmied Sam + +Name = "Sam" +Outfit = (131,57-112-48-95) +Home = [32360,32199,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$","sam",! -> "Hi %N. Can I do something for you?" +ADDRESS,"hi$","sam",! -> * +ADDRESS,"hello$",! -> "Welcome to my shop, adventurer %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and come again." + +"bye" -> "Good bye and come again.", Idle +"farewell" -> * +"job" -> "I am the blacksmith. If you need weapons or armor - just ask me." +"shop" -> * +"name" -> "My name is Samuel, but you can call me Sam." +"time" -> "It is %T." +"king" -> "The king supports Tibia's economy a lot." +"tibianus" -> * +"quentin" -> "He is a monk of some kind!" +"lynda" -> "Uhm! ---blush---" +"harkath" -> "A warrior who is a joy for Banor." +"general" -> * +"army" -> "I supply the army with weapons and armor." +"ferumbras" -> "A threat for mankind! Buy weapons to be ready to face him." +"sam" -> "I was named after my grandfather." +"gorn" -> "He can tell a tale or two about his adventures with baxter in their younger days." +"frodo" -> "I don't like crowded places like his bar." +"elane" -> "Oh, I hardly know her." +"muriel" -> "Sorcerers seldom need my skills." +"gregor" -> "His guild relies heavily on my wares." +"marvik" -> "I never visited his ... cave or whatever it's called." +"bozo" -> "He is funny now and then." +"baxter" -> "A fine warrior." +"oswald" -> "Oswald isn't one of the most liked people in this city." +"sherry" -> "The McRonalds are the local farmers, aren't they?" +"donald" -> * +"mcronald" -> * +"lugri" -> "I just know some rumours that he is a follower of evil." +"excalibug" -> "It is rumoured to be a weapon beyond mortal craftsmanship." +"news" -> "I know nothing of interest." +"help" -> "I sell and buy weapons, armor, helmets, and shields. So you are able to slash the monsters." +"monster" -> "Yeah, these awful beasts. They live in the forests near the city and in the sewers and dungeons." +"dungeon" -> "Below our city are the sewers and I heard about a passage to the deeper dungeons." +"sewer" -> * +"passage" -> "Don't ask me. I have never been there." +"thanks" -> "You are welcome." +"thank","you" -> * + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain and brass armors. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell a dagger for %P gold?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell a spear for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell %A spears for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +"sell","magic","plate","armor" -> "WOW! Do you really want to sell me a MAGIC plate armor?", Topic=3 +Topic=3,"yes" -> Type=3366, Amount=1, Price=6400,"Oh, unbelievable! I would pay %P gold for this wonderful piece of armor. Are you still interested?", Topic=4 +Topic=3 -> "Hmmm, what a pity! I am looking for such an armor since I live in Thais." +Topic=4,"yes",Count(Type)>=Amount -> "Finally it is mine! Here is your money. Can I be of any further help?", Delete(Type), CreateMoney +Topic=4,"yes" -> "Argl! You do not have one! Trying to tease me? Get lost or I call the guards!",Idle +Topic=4 -> "Maybe my offer is too low? Unfortunately I can not bring up more money, I am just a smith." + +"backpack",QuestValue(289)>0 -> "Yes, you brought back my old backpack. Thank you again." +"backpack",QuestValue(289)<1 -> Type=3244, Amount=1, "What? Are you telling me you found my old adventurer's backpack that I lost years ago??",Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Thank you verry much! This brings back good old memories! Please, as a reward, travel to kazordoon and ask my old friend Kroox to provide you a special dwarven armor. ...", + "I will mail him about you immediately. Just tell him, his old buddy sam is sending you.", Delete(Type),SetQuestValue(289,1) + +Topic=5,"yes",Count(Type) "No, you don't have my old backpack. What a pity." +Topic=5,"no" -> "What a pity." + + + +} diff --git a/data/npc/sandra.npc b/data/npc/sandra.npc new file mode 100644 index 0000000..90ead83 --- /dev/null +++ b/data/npc/sandra.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sandra.npc: Datenbank für die Trankhändlerin Sandra + +Name = "Sandra" +Outfit = (137,115-63-95-38) +Home = [33258,31840,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. Please wait a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye and please come back soon." + +"bye" -> "Good bye and please come back soon.", Idle +"name" -> "I am lady Sandra Astralian." +"job" -> "I sell potions and magic fluids." +"time" -> "Buy a watch." +"king" -> "I was guest at his castle on my visits to Thais." +"tibianus" -> * +"army" -> "Do I look as if I'd fraternize with such people?" +"ferumbras" -> "A disgusting person indeed." +"excalibug" -> "I am not interested in tales only kids belive in." +"thais" -> "A city full of disgusting people with ill manners." +"tibia" -> "The world is a place of barbarianism." +"carlin" -> "I plan to visit this city one day." +"edron" -> "Isn't it a wonderful town?" +"news" -> "Nothing I would talk to you about." +"rumors" -> * + +"offer" -> "I'm selling life and mana fluids and several potions." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"fluid" -> * +"potion" -> "I'm selling potions of slime, blood, urine, oil, and distilled water." + +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +"slime" -> Type=2874, Data=6, Amount=1, Price=12, "Do you want to buy a potion of slime for %P gold?", Topic=2 +"blood" -> Type=2874, Data=5, Amount=1, Price=15, "Do you want to buy a potion of blood for %P gold?", Topic=2 +"urine" -> Type=2874, Data=8, Amount=1, Price=10, "Do you want to buy a potion of urine for %P gold?", Topic=2 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy a potion of oil for %P gold?", Topic=2 +"water" -> Type=2874, Data=1, Amount=1, Price=8, "Do you want to buy a potion of distilled water for %P gold?", Topic=2 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." +} diff --git a/data/npc/sarina.npc b/data/npc/sarina.npc new file mode 100644 index 0000000..eafed6d --- /dev/null +++ b/data/npc/sarina.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sarina.npc: Datenbank für die Händlerin Sarina + +Name = "Sarina" +Outfit = (136,41-72-95-96) +Home = [32334,31808,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am talking to a customer. Please stand in line.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment of all kinds. Do you need anything?" +"name" -> "I am Sarina. I am selling everything the adventurer needs." +"time" -> "It is exactly %T. Maybe you want to buy a watch?" +"food" -> "Sorry, I don't sell food." +"ghostlands" -> "Since the druids sealed that placed with their magic, rarely anyone was there. Perhaps whatever haunted that place is long gone, who knows." + +"equipment" -> "I sell shovels, picks, scythes, fishing rods, sixpacks of worms, bags, ropes, backpacks, plates, cups, scrolls, documents, parchments, footballs, and watches. I also sell means of illumination." +"offer" -> * +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"illumination" -> "I sell torches, candlesticks, candelabra, and oil." + +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"bag" -> Type=2860, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2868, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"cup" -> Type=2881, Amount=1, Price=3, "Do you want to buy a cup for %P gold?", Topic=1 +"plate" -> Type=2905, Amount=1, Price=6, "Do you want to buy a plate for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=50, "Do you want to buy a rope for %P gold?", Topic=1 +"scythe" -> Type=3453, Amount=1, Price=50, "Do you want to buy a scythe for %P gold?", Topic=1 +"pick" -> Type=3456, Amount=1, Price=50, "Do you want to buy a pick for %P gold?", Topic=1 +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=150, "Do you want to buy a fishing rod for %P gold?", Topic=1 + +"torch" -> Type=2920, Amount=1, Price=2, "Do you want to buy a torch for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=3, "Do you want to buy a candlestick for %P gold?", Topic=1 +"candelab" -> Type=2911, Amount=1, Price=8, "Do you want to buy a candelabrum for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you want to buy oil for %P gold?", Topic=1 + +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"bag" -> Type=2860, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2868, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"cup" -> Type=2881, Amount=%1, Price=3*%1, "Do you want to buy %A cups for %P gold?", Topic=1 +%1,1<%1,"plate" -> Type=2905, Amount=%1, Price=6*%1, "Do you want to buy %A plates for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=50*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"scythe" -> Type=3453, Amount=%1, Price=50*%1, "Do you want to buy %A scythes for %P gold?", Topic=1 +%1,1<%1,"pick" -> Type=3456, Amount=%1, Price=50*%1, "Do you want to buy %A picks for %P gold?", Topic=1 +%1,1<%1,"shovel" -> Type=3457, Amount=%1, Price=50*%1, "Do you want to buy %A shovels for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=150*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=3*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"candelab" -> Type=2911, Amount=%1, Price=8*%1, "Do you want to buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you want to buy %A vials of oil for %P gold?", Topic=1 + +"worm" -> "I sell worms only in six-packs for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/scott.npc b/data/npc/scott.npc new file mode 100644 index 0000000..76ca6ea --- /dev/null +++ b/data/npc/scott.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# scott.npc: Datenbank für den Wirt Scott auf Senja + +Name = "Scott" +Outfit = (131,75-38-77-96) +Home = [32138,31659,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to my little inn, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "I hope to see you again." + +"bye" -> "I hope to see you again.", Idle +"farewell" -> * +"job" -> "I'm the keeper of the inn. You can buy food here." +"name" -> "My name is Scott." +"time" -> "It is exactly %T." + +"tibia" -> "Oh, I'm happy to live in this world full of thrilling things." +"thais" -> "It's the capital in the southwest of Tibia." +"carlin" -> "Sometimes I travel to Carlin and visit the market." +"queen" -> "She is a strong and wise leader. We owe protection from evil monsters to her." +"senja" -> "It's a peaceful island. Cold and lonesome but I like it." +"mage" -> "It is said that there are some secrets to discover around the mage's castle." +"castle" -> * + +"do","you","sell" -> "You can get bread, cheese, ham, or meat." +"do","you","have" -> * +"offer" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/seymour.npc b/data/npc/seymour.npc new file mode 100644 index 0000000..eea8f74 --- /dev/null +++ b/data/npc/seymour.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# seymour.npc: Datenbank fuer den Schulleiter Seymour + +Name = "Seymour" +Outfit = (128,115-69-87-116) +Home = [32103,32195,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello",! -> "Hello, %N. What do you need?" +ADDRESS,"hi",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "%N, I am already talking to somebody else! Please wait until it is your turn.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye! And remember: No running up and down in the academy!" + +"bye" -> "Good bye! And remember: No running up and down in the academy!", Idle +"farewell" -> * +"how","are","you" -> "Well, the king doesn't send troops anymore, the academy is dreadfully low on money, and the end of the world is pretty nigh. Apart from that I am reasonably fine, I suppose." +"sell" -> "I sell the Key to Adventure for 5 gold! If you are interested, tell me that you want to buy the key." +"job" -> "I am the master of this fine academy." +"academy" -> "Our academy has a library, a training center in the cellars and the oracle upstairs." +"library" -> "Go and read our books. Ignorance may mean death, so be careful." +"train" -> "You can try some basic things down there, but don't challenge the monsters in our arena if you are inexperienced." +"center" -> * +"cellar" -> * +"oracle" -> "You will find the oracle upstairs. Talk to the oracle as soon as you have made level 8. Choose a vocation and a new home town, and you will be sent off to the continent." +"vocation" -> * + +"key" -> Type=2969, Data=4600, Amount=1, Price=5, "Do you want to buy the Key to Adventure for %P gold coins?", Topic=1 +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Only nonsense on your mind, eh?" + +"rookgaard" -> "Here on Rookgaard we have some people, a temple, some shops, a farm and an academy." +"name" -> "My name is Seymour, but to you I am 'Sir' Seymour." +"seymour" -> * +"sir" -> "At least you know how to address a man of my importance." +"time" -> "It is %T, so you are late. Hurry!" +"help" -> "I can assist you with my advice." +"hint" -> * +"people" -> "Well, there's me, Cipfried, Willie, Obi, Amber, Dallheim, Al Dee, Norma, and Hyacinth." +"advice" -> "Read the blackboard for some hints and visit the training center in the cellar." +"monster" -> "You can learn about Tibia's monsters in our library." +"dungeon",level<3 -> "There are some dungeons on this isle, but almost all of them are too dangerous for you at the moment." +"dungeon",level>2 -> "There are some dungeons on this isle. You should be strong enough to explore them now, but make sure to take a rope with you." +"sewer" -> "Our sewers are overrun with rats. If you own some equipment you could go down a sewer grate and fight the vermin." +"god" -> "You can learn much about Tibia's gods in our library." +"gamemaster" -> "If you have serious problems with the game or with other people who are harassing you, contact a counsellor or a gamemaster using CTRL+R." +"counsellor" -> * +"king" -> "Hail to King Tibianus! Long live our king! Not that he cares for an old veteran who is stuck on this godforsaken island..." +"obi" -> "A cousin of Thais' smith Sam. He has a shop here where you can buy most stuff an adventurer needs." +"cipfried" -> "A humble monk with healing powers, and a pupil of the great Quentin himself." +"amber" -> "A traveller from the main land. I wonder what brought her here, since no one comes here of his own free will." +"willie" -> "Willie is a fine farmer, although he has short temper." +"hyacinth" -> "A mysterious druid who lives somewhere in the wilderness. He sells precious life fluids." +"dallheim" -> "Oh good Dallheim! What a fighter he is! Without him we would be doomed." +"al","dee" -> "He is a shop owner in the northwestern part of the village." +"quentin" -> "He is responsible for the temple in Thais." +"life","fluid" -> "A rare magic potion that restores health." +"fuck",male -> "For this remark I will wash your mouth with soap, young man!", EffectOpp(8) +"fuck",female -> "For this remark I will wash your mouth with soap, young lady!", EffectOpp(8) +"bug" -> "Nasty little creatures, but once you have a suitable weapon and perhaps a shield they will be no match for you." +"weapon" -> "You need fine weapons to fight the tougher beasts. Unfortunately only the most basic weapons and armor are available here. You will have to fight some monsters to get a better weapon." +"magic" -> "The only magic-user on this isle is old Hyacinth." +"tibia" -> "Oh, how I miss the crowded streets of Thais. I know one day I will get promoted and get a job at the castle... I must get out of here! The faster the better! It is people like you who are driving me mad." +"castle" -> "The castle of Thais is the greatest achievement in Tibian history." + +"mission",level<4 -> "You are pretty inexperienced. I think killing rats is a suitable challenge for you. For each fresh rat I will give you two shiny coins of gold." +"quest",level<4 -> * +"mission",level>3 -> "Well I would like to send our king a little present, but I do not have a suitable box. If you find a nice box, please bring it to me." +"quest",level>3 -> * + +"rat" -> Type=3994, Amount=1, Price=2, "Have you brought a dead rat to me to pick up your reward?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "Have you brought %A dead rats to me to pick up your reward?", Topic=2 +Topic=2,"yes",Count(Type)>=Amount -> "Thank you! Here is your reward.", Delete(Type), CreateMoney +Topic=2,"yes" -> "HEY! You don't have one! Stop playing tricks on fooling me or I will give you some extra work!" +Topic=2,"yes",Amount>1 -> "HEY! You do not have so many!" +Topic=2 -> "Go and find some rats to kill!" + +"box" -> Type=2856, Amount=1, "Do you have a suitable present box for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "THANK YOU! Here is a helmet that will serve you well.", Delete(Type), Create(3374) +Topic=3,"yes" -> "HEY! You don't have one! Stop playing tricks on me or I will give some extra work!" +Topic=3 -> * +} diff --git a/data/npc/shalmar.npc b/data/npc/shalmar.npc new file mode 100644 index 0000000..0970376 --- /dev/null +++ b/data/npc/shalmar.npc @@ -0,0 +1,218 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shalmar.npc: Datenbank fuer den Magier Shalmar + +Name = "Shalmar" +Outfit = (130,95-8-65-0) +Home = [33221,32408,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings.", Idle +"name" -> "I am Shalmar Ibn Djinbar, the caliph's magician and astrologer." +"job" -> "I teach magic spells to the worthy." +"time" -> "It's %T right now." +"caliph" -> "The caliph has the strong soul needed to guide his people." +"kazzan" -> * +"ferumbras" -> "His weakness is evident by the rotting of his soul." +"excalibug" -> "A strong mind and a pure soul has no need for such items." +"thais" -> "It's a city of souls who failed to see the need of ascension." +"tibia" -> "The world is filled with wonderous places and items." +"carlin" -> "I heared it's a city of druids." +"ascension" -> "Talk to Kasmir about that issue. It's not my place to pose as a teacher since I am a student, too." +"news" -> "News are distractions. Nothing of importance happens outside your own soul." +"rumour" -> * +"rumor" -> * +"sorcerer" -> "The way of the magician is not that different from the way to ascension." +"druid" -> * + +"offer" -> "I'm teaching spells to sorcerers and druids. I also used to sell magic goods, but my assistant Asima in the next room does that now for me." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I don't sell this anymore, it sort of kept on confusing me to do that much work. Please talk to my assistant Asima in the next room to purchase magic goods." +"life","fluid" -> * +"mana","fluid" -> * +"blank","rune" -> * +"spellbook" -> * + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=3 +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers and Druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Daraman's blessings.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=4 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=4 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=4 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=4 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=4 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=4 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=4 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=4 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=4 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=4 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=4 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=4 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=4 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=4 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=4 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=4 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=4 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=3,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=3,"level" -> "For which level would you like to learn a spell?", Topic=3 +Topic=3,"bye" -> "Daraman's blessings.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=3 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=4 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=4 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=4 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=4 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=4 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=4 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=4 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=4 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=4 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=4 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=4 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=4 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=4 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=4 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=4 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=4 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=4 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=4 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=4 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=4 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=4 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=4 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=4 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=4 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=4 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=4 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=4 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=4 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=4 + + +Topic=3,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=3 +Topic=3,"9$" -> "For level 9 I have 'Light Healing'.", Topic=3 +Topic=3,"10$" -> "For level 10 I have 'Antidote'.", Topic=3 +Topic=3,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=3 +Topic=3,"13$" -> "For level 13 I have 'Great Light'.", Topic=3 +Topic=3,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=3 +Topic=3,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=3 +Topic=3,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=3 +Topic=3,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=3 +Topic=3,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=3 +Topic=3,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=3 +Topic=3,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=3 +Topic=3,"27$" -> "For level 27 I have 'Firebomb'.", Topic=3 +Topic=3,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=3 +Topic=3,"31$" -> "For level 31 I have 'Explosion'.", Topic=3 +Topic=3,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=3 +Topic=3,"35$" -> "For level 35 I have 'Invisible'.", Topic=3 +Topic=3,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=3 +Topic=3,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=3 +Topic=3,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=3 + +Topic=3 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=3 + +Topic=4,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=4,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=4,"yes",CountMoney "You need more money." +Topic=4,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=4 -> "Maybe next time." + + +} diff --git a/data/npc/shanar.npc b/data/npc/shanar.npc new file mode 100644 index 0000000..27223d6 --- /dev/null +++ b/data/npc/shanar.npc @@ -0,0 +1,216 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shanar.npc: Datenbank für den Schmied Shanar (Elfenstadt) + +Name = "Shanar" +Outfit = (144,0-93-105-76) +Home = [32657,31655,8] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I sell weapons, shields, and armor, and teach protective spells." +"name" -> "I am Shanar Ethkal." +"time" -> "I don't care." + +"carlin" -> "Carlin is quite close and we have some trade now and then." +"thais" -> "Thais is a town of humans far away." +"venore" -> "Those human merchants try to sell their low quality weapons and armor here to ruin my trade." +"roderick" -> "He is some human who lives in a stone house in the south of the town." +"olrik" -> "This human is sneaking around in the tow far too often." + +"elves" -> "That's our race, so what?" +"dwarfs" -> "Ugly and dirty." +"humans" -> "Loud and noisy." +"troll" -> "I own only a few." +"cenath" -> "Idiots." +"kuridai" -> "That's my caste." +"deraisim" -> "Squirrels." +"abdaisim" -> "They don't live here." +"teshial" -> "Don't know much about them" +"ferumbras" -> "A danger to all." +"crunor" -> "I don't care about gods." +"excalibug" -> "Perhaps more than a myth." +"news" -> "Nothing I want to talk about." +"magic" -> "I teach some spells of protection." +"druid" -> "Druids are great healers." +"sorcerer" -> "They understand so few..." + +"sell","coat" -> Type=3562, Amount=1, Price=1, "Do you want to sell a coat for %P gold?", Topic=2 +"sell","jacket" -> Type=3561, Amount=1, Price=1, "Do you want to sell a jacket for %P gold?", Topic=2 +"sell","knight","armor" -> Type=3370, Amount=1, Price=875, "Do you want to sell a knight's armor for %P gold?", Topic=2 +"sell","golden","armor" -> Type=3360, Amount=1, Price=1500,"Do you want to sell a golden armor for %P gold?", Topic=2 +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","brass","helmet" -> Type=3354, Amount=1, Price=30, "Do you want to sell a brass helmet for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=145, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","devil","helmet" -> Type=3356, Amount=1, Price=450, "Do you want to sell a devil's helmet for %P gold?", Topic=2 +"sell","warrior","helmet" -> Type=3369, Amount=1, Price=696, "Do you want to sell a warrior's helmet for %P gold?", Topic=2 +"sell","leather","legs" -> Type=3559, Amount=1, Price=1, "Do you want to sell leather legs for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=20, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","brass","legs" -> Type=3372, Amount=1, Price=49, "Do you want to sell brass legs for %P gold?", Topic=2 +"sell","plate","legs" -> Type=3557, Amount=1, Price=115, "Do you want to sell plate legs for %P gold?", Topic=2 +"sell","knight","legs" -> Type=3371, Amount=1, Price=375, "Do you want to sell knight's legs for %P gold?", Topic=2 + +"sell",%1,1<%1,"coat" -> Type=3562, Amount=%1, Price=1*%1, "Do you want to sell %A coats for %P gold?", Topic=2 +"sell",%1,1<%1,"jacket" -> Type=3561, Amount=%1, Price=1*%1, "Do you want to sell %A jackets for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","armor" -> Type=3370, Amount=%1, Price=875*%1, "Do you want to sell %A knight's armors for %P gold?", Topic=2 +"sell",%1,1<%1,"golden","armor" -> Type=3360, Amount=%1, Price=1500*%1,"Do you want to sell %A golden armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","helmet" -> Type=3354, Amount=%1, Price=30*%1, "Do you want to sell %A brass helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=145*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"devil","helmet" -> Type=3356, Amount=%1, Price=450*%1, "Do you want to sell %A devil's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"warrior","helmet" -> Type=3369, Amount=%1, Price=696*%1, "Do you want to sell %A warrior's helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","legs" -> Type=3559, Amount=%1, Price=1*%1, "Do you want to sell %A leather legs for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=20*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","legs" -> Type=3372, Amount=%1, Price=49*%1, "Do you want to sell %A brass legs for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","legs" -> Type=3557, Amount=%1, Price=115*%1, "Do you want to sell %A plate legs for %P gold?", Topic=2 +"sell",%1,1<%1,"knight","legs" -> Type=3371, Amount=%1, Price=375*%1, "Do you want to sell %A knight's legs for %P gold?", Topic=2 + +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","brass","shield" -> Type=3411, Amount=1, Price=16, "Do you want to sell a brass shield for %P gold?", Topic=2 +"sell","plate","shield" -> Type=3410, Amount=1, Price=31, "Do you want to sell a plate shield for %P gold?", Topic=2 +"sell","guardians","shield" -> Type=3415, Amount=1, Price=180, "Do you want to sell a guardian's shield for %P gold?", Topic=2 +"sell","dragon","shield" -> Type=3416, Amount=1, Price=360, "Do you want to sell a dragon shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","shield" -> Type=3411, Amount=%1, Price=16*%1, "Do you want to sell %A brass shields for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","shield" -> Type=3410, Amount=%1, Price=31*%1, "Do you want to sell %A plate shields for %P gold?", Topic=2 +"sell",%1,1<%1,"guardians","shield" -> Type=3415, Amount=%1, Price=180*%1, "Do you want to sell %A guardian's shields for %P gold?", Topic=2 +"sell",%1,1<%1,"dragon","shield" -> Type=3416, Amount=%1, Price=360*%1, "Do you want to sell %A dragon shields for %P gold?", Topic=2 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "Do you want to sell a longsword for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000,"Do you want to sell a fire sword for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=23, "Do you want to sell a mace for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "Do you want to sell %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1,"Do you want to sell %A fire swords for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "Do you want to sell %A maces for %P gold?", Topic=2 + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"staff" -> Type=3289, Amount=1, Price=40, "Do you want to buy it for %P gold?", Topic=1 +"longsword" -> Type=3285, Amount=1, Price=160, "Do you want to buy it for %P gold?", Topic=1 +"machete" -> Type=3308, Amount=1, Price=35, "Do you want to buy a machete for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"staff" -> Type=3289, Amount=%1, Price=40*%1, "Do you want to buy them for %P gold?", Topic=1 +%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=160*%1, "Do you want to buy them for %P gold?", Topic=1 +%1,1<%1,"machete" -> Type=3308, Amount=%1, Price=35*%1, "Do you want to buy %A machetes for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2 -> "Ok, then not." + +"spell" -> "I teach 'Poison Field', 'Fire Field', 'Energy Field', 'Poison Wall', 'Fire wall', and 'Energy Wall'." + +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +"poison","field" -> "I'm sorry, but this spell is only for druids." +"fire","field" -> * +"energy","field" -> * +"poison","wall" -> * +"fire","wall" -> * +"energy","wall" -> * + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know that spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You need to improve to level %A to learn this spell." +Topic=3,"yes",CountMoney "You do not have enough gold to pay my services." +Topic=3,"yes" -> "From now on you may cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "As you wish." + +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." + +"weapon" -> "I have spears, swords, rapiers, daggers, longswords, machetes, staffs, and sabres. Interested?" +"helmet" -> "I am selling leather helmets and chain helmets. Anything you'd like to buy?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * +"spellbook" -> "I have none here." +} diff --git a/data/npc/shauna.npc b/data/npc/shauna.npc new file mode 100644 index 0000000..ef34c0b --- /dev/null +++ b/data/npc/shauna.npc @@ -0,0 +1,79 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shauna.npc: Datenbank für den Sheriff von Carlin Shauna + +Name = "Shauna" +Outfit = (139,78-95-38-58) +Home = [32384,31778,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$",! -> "Howdy!" +ADDRESS,"salutations$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Shut up!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE QUEEN!" + +"bye" -> "LONG LIVE THE QUEEN!", Idle +"news" -> "No news are good news." +"queen" -> "HAIL TO QUEEN ELOISE!" +"leader" -> * +"job" -> "What do you think? I am the sheriff of Carlin." +"how","are","you"-> "Just fine." + +"sell" -> "Would you like to buy the general key to the town?", Topic=1 +Topic=1,"yes" -> "Yeah, I bet you'd like to do that! HO, HO, HO!" + +"army" -> "If they make trouble, I'll put them behind bars like all others." +"guard" -> * +"general" -> "The Bonecrusher family is ideally suited for military jobs." +"bonecrusher" -> * +"enemies" -> "If you have a crime to report and clues, then do it, but dont waste my time." +"enemy" -> * +"criminal" -> * +"murderer" -> * +"castle" -> "The castle is one of the safest places in Carlin." +"subject" -> "Our people are fine and peaceful." +"tbi$" -> "I bet they spy on us... not my business, however." +"todd$" -> "I scared this bigmouth so much that he left the town by night. HO, HO, HO!" +"city" -> "The city is is a peacful place, and it's up to me to keep it this way." +"hain$" -> "He is the guy responsible to keep the sewers working. Someone has to do such kind of jobs. I can't handle all the garbage of the city myself." +"rowenna$" -> "Rowenna is one of our local smiths. When you look for weapons, look for Rowenna." +"weapon" -> * +"Cornelia" -> "Cornelia is one of our local smiths. When you look for armor, look for Rowenna." +"armor" -> * +"legola" -> "She has the sharpest eye in the region, I'd say." +"padreia" -> "Her peacefulness is sometimes near stupidity." +"god" -> "I worship Banor of course." +"banor" -> "For me, he's the god of justice." +"zathroth" -> "His cult is forbidden in our town." +"brog" -> "Wouldn't wonder if some males worship him secretly. HO, HO, HO!" +"monster" -> "I deal more with the human mosters, you know? HO, HO, HO!" +"excalibug" -> "Would certainly make a good butterknife. HO, HO, HO!" +"rebellion" -> "The only thing that rebels here now and then is the stomach of a male after trying to make illegal alcohol. HO, HO, HO!" +"alcohol" -> "For obvious reasons it's forbidden in our city." + +"waterpipe" -> "Oh, there's a waterpipe in one of my cells? ...", + "I guess my last prisoner forgot it there." +"pipe" -> * +"prisoner" -> "My last prisoner? Hmm. ...", + "I think he was some guy from Darama. Can't remember his name. ...", + "He was here just for one night, because he got drunk and annoyed our citizens. ...", + "Obviously he wasn't pleased with this place, because he headed for Thais the next day. ...", + "Something tells me that he won't stay out of trouble for too long." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/data/npc/sherry.npc b/data/npc/sherry.npc new file mode 100644 index 0000000..71ed8b6 --- /dev/null +++ b/data/npc/sherry.npc @@ -0,0 +1,78 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sherry.npc: Datenbank für die Bäuerin Sherry McRonald + +Name = "Sherry McRonald" +Outfit = (136,78-94-38-58) +Home = [32390,32241,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Welcome to our humble farm." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a minute, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "What a strange person." + +"bye" -> "Grace our home with another visit soon.", Idle +"farewell" -> * +"job" -> "I and my husband run this farm." +"husband" -> "My husband Donald is busy on the fields almost all night and day." +"donald" -> * +"farm" -> "It is a hard work, but the city needs us." +"name" -> "I am Sherry McRonald." +"time" -> "Sorry, I don't have a watch." +"weather" -> "The weather is the best friend and the worst enemy of a farmer." +"field" -> "The druids helped us by placing a blessing on our fields." +"city" -> "The city needs our crops." +"crops" -> "It's hard to harvest it, carry it to the mill in the north and make flour. If you can bake some bread I will buy it for 2 gold." +"mill" -> "The miller is a lazy fellow and afraid of his own mill, because he thinks it is spooked." +"spooked" -> "I don't know for sure. The miller claims that his mill is threatened by some monsters sometimes." +"king" -> "King Tibianus granted us this farm to earn a living." +"tibianus" -> * +"frodo" -> "He is a friend of my husband." +"oswald" -> "This lazy fellow has nothing better to do than to spread rumours." +"bloodblade" -> "He is an impressive warrior as far as I can tell." +"muriel" -> "We a mere peasants and don't know much about the guild leaders." +"elane" -> * +"gregor" -> * +"marvik" -> * +"gorn" -> "He doesn't talk much to us." +"sam" -> "He is too busy to care much about farmers like us." +"quentin" -> "What a nice person he is." +"lynda" -> "She is sooo charming. I can't believe she is not married yet! Have you met her?", Topic=2 +"spider" -> "Spiders infested the sewers beneath our farm. We need some help to exterminate them. My husband pays a reward for killed spiders." +"monster" -> * +"help" -> * + +"buy" -> "I can offer you cheese, cherries, and melons." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "Are you looking for food? I have cheese, cherries, pumpkins and melons." + +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy cheese for %P gold?", Topic=1 +"cherry" -> Type=3590, Amount=1, Price=1, "Do you want to buy a cherry for %P gold?", Topic=1 +"melon" -> Type=3593, Amount=1, Price=8, "Do you want to buy a melon for %P gold?", Topic=1 +"pumpkin" -> Type=3594, Amount=1, Price=10, "Do you want to buy a pumpkin for %P gold?", Topic=1 + +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "Do you want to buy %A cherries for %P gold?", Topic=1 +%1,1<%1,"melon" -> Type=3593, Amount=%1, Price=8*%1, "Do you want to buy %A melons for %P gold?", Topic=1 +%1,1<%1,"pumpkin" -> Type=3594, Amount=%1, Price=10*%1, "Do you want to buy %A pumpkins for %P gold?", Topic=1 + + +"sell","bread" -> "I will pay 2 gold for every bread, is that ok?", Topic=3 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +Topic=2,"yes",male -> "She really should find a husband with ease! You should ask her for a date." +Topic=2,"yes",female -> "She really should find a husband with ease! If you know a bachelor, introduce him to her." +Topic=2 -> "Oh, if you say so." + +Topic=3,"yes",Count(3600)>0 -> Amount=Count(3600), Price=Amount*2, "Here you are ... %P gold.", Delete(3600), CreateMoney +Topic=3,"yes" -> "Sorry, you don't have any bread." +Topic=3 -> "Maybe another time." +} diff --git a/data/npc/shiantis.npc b/data/npc/shiantis.npc new file mode 100644 index 0000000..0c4d80a --- /dev/null +++ b/data/npc/shiantis.npc @@ -0,0 +1,99 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shiantis.npc: Datenbank für die Händlerin Shiantis in Venore + +Name = "Shiantis" +Outfit = (136,0-36-13-76) +Home = [32890,32086,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N. What is your need today?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Excuse me, %N, I am already talking to another customer. Wait just a moment.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling several kinds of equipment and decoration. What is your need?" +"name" -> "I am Shiantis." +"time" -> "I won't tell you for free, but maybe you want to buy a watch?" +"king" -> "I would love to see the royal taxes lowered." +"tibianus" -> * +"army" -> "I think it's needed for protection. We pay enough taxes for this." +"ferumbras" -> "Ferumbras dolls were not the saleshit we expected. Sold all stock to a strange guy who bought a bunch of needles, too." +"excalibug" -> "Sorry, we run out of stock. I expect another load of wodden excalibug simulacra to arrive next week." +"news" -> "I heard the merchants will petition the king to lower the taxes." +"tax" -> * + +"offer" -> "As you can see, our inventory is large, just have a look." +"goods" -> "At this booth we sell containers, decoration, illumination, paperware, footballs, and watches." +"do","you","sell" -> * +"do","you","have" -> * +"equipment" -> * +"containers" -> "In that department we offer bags, backpacks, and present boxes." +"illumination" -> "In that department we offer torches, candlesticks, candelabra, oil and coal basins." +"paperware" -> "In that department we offer scrolls, documents, parchments, and books." +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" +"magic" -> "You will have to visit that spooky magic market for that stuff." +"fluid" -> * + +"torch" -> Type=2920, Amount=1, Price=2, "Do you wanna buy a torch for %P gold?", Topic=1 +"candelabr" -> Type=2911, Amount=1, Price=8, "Do you wanna buy a candelabrum for %P gold?", Topic=1 +"candlestick" -> Type=2917, Amount=1, Price=2, "Do you want to buy a candlestick for %P gold?", Topic=1 +"oil" -> Type=2874, Data=7, Amount=1, Price=20, "Do you wanna buy oil for %P gold?", Topic=2 +"coal","basin" -> Type=2806, Amount=1, Price=25, "Do you want to buy a coal basin for %P gold?", Topic=3 +"bag" -> Type=2859, Amount=1, Price=5, "Do you want to buy a bag for %P gold?", Topic=1 +"backpack" -> Type=2867, Amount=1, Price=20, "Do you want to buy a backpack for %P gold?", Topic=1 +"present" -> Type=2856, Amount=1, Price=10, "Do you want to buy a present for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"watch" -> Type=2906, Amount=1, Price=20, "Do you want to buy one of my high quality watches for %P gold?", Topic=1 +"football" -> Type=2990, Amount=1, Price=111, "Do you want to buy a football for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=2*%1, "Do you wanna buy %A torches for %P gold?", Topic=1 +%1,1<%1,"candelabr" -> Type=2911, Amount=%1, Price=8*%1, "Do you wanna buy %A candelabra for %P gold?", Topic=1 +%1,1<%1,"candlestick" -> Type=2917, Amount=%1, Price=2*%1, "Do you want to buy %A candlesticks for %P gold?", Topic=1 +%1,1<%1,"oil" -> Type=2874, Data=7, Amount=%1, Price=20*%1, "Do you wanna buy %A vials of oil for %P gold?", Topic=2 +%1,1<%1,"coal","basin" -> Type=3510, Amount=%1, Price=25*%1, "Do you want to buy %A coal basins for %P gold?", Topic=3 +%1,1<%1,"bag" -> Type=2859, Amount=%1, Price=5*%1, "Do you want to buy %A bags for %P gold?", Topic=1 +%1,1<%1,"backpack" -> Type=2867, Amount=%1, Price=20*%1, "Do you want to buy %A backpacks for %P gold?", Topic=1 +%1,1<%1,"present" -> Type=2856, Amount=%1, Price=10*%1, "Do you want to buy %A presents for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"watch" -> Type=2906, Amount=%1, Price=20*%1, "Do you want to buy %A of my high quality watches for %P gold?", Topic=1 +%1,1<%1,"football" -> Type=2990, Amount=%1, Price=111*%1, "Do you want to buy %A footballs for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Ok, take it. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "That's not funny!" +Topic=2 -> "Then not." + +Topic=3,"yes",Premium,CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=3,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account if you want to buy furniture." +Topic=3,"yes" -> "Come back, when you have enough money." +Topic=3 -> "Hmm, but I'm sure, it would fit nicely into your house." + +"deposit" -> "I will give you 5 gold for every empty vial. Ok?", Data=0, Topic=4 +"vial" -> * +"flask" -> * + +Topic=4,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=4,"yes" -> "You don't have any empty vials." +Topic=4 -> "Hmm, but please keep our town litter free." + +@"gen-t-furniture-decoration-s.ndb" +} diff --git a/data/npc/shiriel.npc b/data/npc/shiriel.npc new file mode 100644 index 0000000..c5b52e7 --- /dev/null +++ b/data/npc/shiriel.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shiriel.npc: Datenbank für die Magiehändlerin Shiriel (Elfenstadt) + +Name = "Shiriel" +Outfit = (144,2-103-0-95) +Home = [32670,31657,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"job" -> "I sell mystic runes, spellbooks, wands, rods and fluids of life or mana." +"name" -> "I am Shiriel Sharaziel." +"time" -> "Time was mastered by my people long ago." + +"elves" -> "Our noble race has knowledge of secrets beyond your comprehension." +"dwarfs" -> "Not worth to bother about." +"humans" -> "Cursed with a short livespan and not worth to be remembered." +"troll" -> "We should eradicate them all." +"cenath" -> "We are the teachers to the other castes." +"kuridai" -> "Their knowledge is limited." +"deraisim" -> "They lack the patience that suits a race with our lifespan." +"abdaisim" -> "I think they are lost forever." +"teshial" -> "They were not prepared for what they encountered in their quest for knowledge. WE will be prepared." +"ferumbras" -> "A humanbreed abnomination." +"crunor" -> "I have no time for superstition." +"excalibug" -> "I would love to analyse it one day." +"news" -> "News are secrets and you are not worthy of my secrets." +"magic" -> "I could teach you some spells ... but I won't." + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +"deposit" -> "I will pay you 5 gold for every empty vial, ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/data/npc/shirith.npc b/data/npc/shirith.npc new file mode 100644 index 0000000..7b3aea9 --- /dev/null +++ b/data/npc/shirith.npc @@ -0,0 +1,52 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# shirith.npc: Datenbank für den Minenleiter Shirith (Elfenstadt) + +Name = "Shirith" +Outfit = (144,59-97-58-76) +Home = [32646,31655,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Asha Thrazi." + +"bye" -> "Asha Thrazi.", Idle +"farewell" -> * +"asha","thrazi" -> * +"job" -> "I am the overseer of the mines." +"name" -> "I am called Shirith Blooddancer." +"time" -> "It is %T." + +"carlin" -> "I think those humans are trespassing elven teritory far too often." +"thais" -> "Thais is far away as all humans should be." +"venore" -> "If it comes to trade, I can respect those merchants. As long as they leave as soon as they finished buisness, that is." +"roderick" -> "We don't need him or any other ambassador here." +"olrik" -> "As a post officer he has some use ... as a troll has some use for mining." + +"elves" -> "We are a superior race, indeed." +"dwarfs" -> "They could be of ... some use." +"human" -> "Humans are more annoying than our trolls." +"troll" -> "We give these useless creatures a reason to live by serving us." +"cenath" -> "They think they are better then us." +"kuridai" -> "We keep this society running. Without our tools and work our case would be a lost one." +"deraisim" -> "They could do more for us if they would try more hard." +"abdaisim" -> "Let them go, we don't need them." +"teshial" -> "Who needs dreamers in these days?" +"ferumbras" -> "He should be destroyed." +"mines" -> "We hardly get the ore we need. The worthless trolls are lazy workers. I keep them locked up the whole time." +"locked" -> "I keep the keys to the mines." +"excalibug" -> "Nonsense." +"news" -> "Trolls are boring, I have no news to tell." + +"key" -> Type=2969, Data=3033, Price=50, "I would sell you a key for 50 gold, ok?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You do not have enough gold." +Topic=1 -> "Ok, then not." +} diff --git a/data/npc/sigurd.npc b/data/npc/sigurd.npc new file mode 100644 index 0000000..0ae178f --- /dev/null +++ b/data/npc/sigurd.npc @@ -0,0 +1,63 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sigurd.npc: Datenbank für den Händler Sigurd + +Name = "Sigurd" +Outfit = (69,0-0-0-0) +Home = [32626,31923,5] +Radius = 8 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the magic store, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "You next %N, jawoll!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Come back soon." + +"bye" -> "Good bye. Come back soon.", Idle +"farewell" -> * + +"name" -> "I am Sigurd Fireworker, brother to Etzel Fireworker, son of fire, of the Molten Rocks." +"job" -> "I help my brother handling his little magic store so he can focus on studying spells." +"time" -> "It's %T right now." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=5 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=5 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=4 +"spellbook" -> Type=3059, Amount=1, Price=150, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy one for %P gold?", Topic=4 + +%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=5 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=5 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=4 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "In a spellbook, your spells are listed. There you will find the pronunciation of each spell. Do you want to buy %A spellbooks for %P gold?", Topic=4 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=6 +"vial" -> * +"flask" -> * + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." + +Topic=5,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "Come back, when you have enough money." +Topic=5 -> "Hmm, but next time." + +Topic=6,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=6,"yes" -> "You don't have any empty vials." +Topic=6 -> "Hmm, but please keep Tibia litter free." + +} diff --git a/data/npc/simon.npc b/data/npc/simon.npc new file mode 100644 index 0000000..a9711e6 --- /dev/null +++ b/data/npc/simon.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# simon.npc: Datenbank für den Bettler Simon auf der Insel Fibula + +Name = "Simon the Beggar" +Outfit = (128,116-123-32-40) +Home = [32186,32468,7] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. I am a poor man. Please help me." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Have a nice day." + +"bye" -> "Have a nice day.", Idle +"farewell" -> * +"job" -> "I have no job. I am a beggar." +"beggar" -> "I have no gold and no job, so I am a beggar." +"gold" -> "I need gold. I love gold. I need help." +"name" -> "My name is Simon. I am a very poor man." +"simon" -> "I am Simon. The poorest human all over the continent." + +"help" -> Price=100, "I need gold. Can you spend me %P gold pieces?", Topic=1 +Topic=1,"yes",CountMoney>=Price -> DeleteMoney, Price=500, "Thank you very much. Can you spend me %P more gold pieces? I will give you a nice hint.", Topic=2 +Topic=1,"yes" -> "You've not enough money for me." +Topic=1 -> "Hmm, maybe next time." +Topic=2,"yes",CountMoney>=Price -> DeleteMoney, Price=200, "That's great! I have stolen something from Dermot. You can buy it for %P gold. Do you want to buy it?", Topic=3 +Topic=2,"yes" -> "Sorry, that's not enough." +Topic=2 -> "It was your decision." +Topic=3,"yes",CountMoney>=Price -> DeleteMoney, "Now you own the hot key.", Data=3940, Create(2968) +Topic=3,"yes" -> "Pah! I said 200 gold. You don't have so much." +Topic=3 -> "Ok. No problem. I'll find another buyer." + +"dermot" -> "The magistrate of the village. I heard he is selling something for the Fibula Dungeon." +"village" -> "To the north is the village Fibula. A very small village." +"key" -> "Key? There are a lot of keys. Please change the topic." +"dungeon" -> "I heard a lot about the Fibula Dungeon. But I never was there." +"fibula" -> "I hate Fibula. Too many wolves are here." +"timur" -> "I hate Timur. He is too expensive. But sometimes I find maces and hatchets. Timur is buying these items." +"wolf" -> "Please kill them ... ALL." +"flute" -> "Har, har. The stupid Dermot lost his flute. I know that some minotaurs have it in their treasure room." +"minotaurs"-> "Very rich monsters. But they are too strong for me. However, there are even stronger monsters." +"minos" -> * +"treasure" -> "I know there are two rooms. And I know you can pass only the first door. The second door can't be opened." +"giant","spider" -> "I know that terrible monster. It killed the fishers on the isle to the north." +"monster" -> "The strongest monster I know is the giant spider." +"jetty" -> "I hate this jetty. I have never seen a ship here." +"ship" -> "There is a large sea-monster outside. I think there is no gritty captain to sail in this quarter." +"tibia" -> "Hehe, do you have a shovel? I can sell you a shovel if you want to return to Tibia." + +"shovel" -> Type=3457, Amount=1, Price=50, "Do you want to buy a shovel for %P gold?", Topic=4 + +Topic=4,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=4,"yes" -> "Come back, when you have enough money." +Topic=4 -> "Hmm, but next time." +} diff --git a/data/npc/skjaar.npc b/data/npc/skjaar.npc new file mode 100644 index 0000000..09f0242 --- /dev/null +++ b/data/npc/skjaar.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# skjaar.npc: Datenbank für Skjaar, Wächter der Krypta im Berg und Meistermagier + +Name = "Skjaar" +Outfit = (9,0-0-0-0) +Home = [32450,32038,8] +Radius = 2 + +Behaviour = { +BUSY,"hello$",Level<15,! -> "I don't talk to little children!!", Idle +BUSY,"hi$",Level<15,! -> * +ADDRESS,"hello$",Druid,! -> "Hail, friend of nature! How may I help you?" +ADDRESS,"hi$",Druid,! -> * +ADDRESS,"hello$",Knight,! -> "Another creature who believes thinks physical strength is more important than wisdom! Why are you disturbing me?" +ADDRESS,"hi$",Knight,! -> * +ADDRESS,"hello$",Sorcerer,! -> "It's good to see somebody who has chosen the path of wisdom. What do you want?" +ADDRESS,"hi$",Sorcerer,! -> * +ADDRESS,"hello$",Paladin,! -> "Neither strong enough to be a knight nor wise enough to be a real mage. You like it easy, don't you? Why are you disturbing me?" +ADDRESS,"hi$",Paladin,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence, unworthy creature!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Run away, unworthy %N!" + +"bye" -> "Farewell, %N!", Idle +"farewell" -> * +"job" -> "Once I was the master of all mages, but now I only protect this crypt." +"name" -> "I am Skjaar the Mage, master of all spells." +"door" -> "This door seals a crypt." +"crypt" -> "Here lies my master. Only his closest followers may enter." +"help" -> "I'm not here to help anybody. I only protect my master's crypt." +"mountain" -> "Hundreds of years my master's castle stood on the top of this mountain. Now there is a volcano." +"volcano" -> "I can still feel the magical energy in the volcano." +"castle" -> "The castle was destroyed when my master tried to summon a nameless creature. All that is left is this volcano." +"time" -> "To those who have lived for a thousand years time holds no more terror." +"master" -> "If you are one of his followers, you need not ask about him, for you will know. And if you aren't, you are not worthy anyway!" +"key" -> "I will give the key to the crypt only to the closest followers of my master. Would you like me to test you?", Topic=1 + +"idiot" -> "Take this for your words!", HP=1, EffectOpp(14), Idle +"fuck" -> "Take this for your words!", HP=1, EffectOpp(14), Idle +"asshole" -> "Take this for your words!", HP=1, EffectOpp(14), Idle + +Topic=1,"yes" -> Price=1000, "Before we start I must ask you for a small donation of 1000 gold coins. Are you willing to pay 1000 gold coins for the test?", Topic=2 +Topic=1,"no" -> "Then leave, unworthy worm!", Idle +Topic=1 -> "You're not worthy if you cannot make up your mind. Leave!", Idle + +Topic=2,"yes",CountMoney>=Price -> "All right then. Here comes the first question. What was the name of Dago's favourite pet?", DeleteMoney, Topic=3 +Topic=2,"yes",CountMoney "You don't even have the money to make a donation? Then go!", Idle +Topic=2,"no" -> "You're not worthy then. Now leave!", Idle +Topic=2 -> "You're not worthy if you cannot make up your mind. Leave!", Idle + +Topic=3,"redips",! -> "Perhaps you knew him after all. Tell me - how many fingers did he have when he died?", Topic=4 +Topic=3,! -> "You are wrong. Get lost!", Idle + +Topic=4,"7",! -> "Also true. But can you also tell me the colour of the deamons in which master specialized?", Topic=5 +Topic=4,"seven",! -> "Also true. But can you also tell me the colour of the deamons in which my master specialized?", Topic=5 +Topic=4,! -> "You are wrong. Get lost!", Idle + +Topic=5,"black",! -> "It seems you are worthy after all. Do you want the key to the crypt?", Topic=6 +Topic=5,! -> "You are wrong. Get lost!", Idle + +Topic=6,"yes" -> Type=2970, Data=3142, Amount=1, "Here you are.", Create(Type) +Topic=6 -> "It is always a wise decision to leave the dead alone." +} diff --git a/data/npc/smiley.npc b/data/npc/smiley.npc new file mode 100644 index 0000000..b28894a --- /dev/null +++ b/data/npc/smiley.npc @@ -0,0 +1,129 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# smiley.npc: Datenbank für den Magiehändler Smiley + +Name = "Smiley" +Outfit = (37,0-0-0-0) +Home = [32979,32087,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "... Greeeeeetiiiingssss..." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "... Wait... %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "... Good... Bye" + +"bye" -> "... Good... Bye", Idle +"farewell" -> * +"job" -> "... Selling Spells" +"name" -> "... Smiley" +"time" -> "... Time?... Not important... anymore." +"king" -> "..." +"tibianus" -> * +"vladruc" -> "... Maaaaassssterrrrr" +"urghain" -> * +"ferumbras" -> "... un...important" +"market" -> "... You buy?" +"excalibug" -> "... only sell spells..." +"news" -> "... more spells..." + +"sorcerer" -> "... Ask Chatterbone?" +"druid" -> "... You... buy spells?" +"power" -> * +"spellbook" -> "... You buy book... store spells... other counter..." +"rune" -> "... Runes... mighty stones... other counter..." +Druid,"spell" -> "... Spells... rune spells... instant spells... what you want? ... Or for which level?", Topic=2 +"spell" -> "... Only druids..." + +druid,"rod",QuestValue(333)<1 -> "Oooh... present from meee... take it... goooood start for youuuung druuuids...",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + + +Topic=2,"rune","spell" -> "... Attack rune spells ... healing rune spells ... support rune spells ... summon rune spells. Which...?" +Topic=2,"instant","spell" -> "... Healing spells ... supply spells ... support spells ... summon spells. Which...?" +Topic=2,"level" -> "Which level...?", Topic=2 +Topic=2,"bye" -> "... Good... Bye", Idle + +Druid,"level" -> "... Spell... which level...?", Topic=2 +Druid,"rune","spell" -> "... Attack rune spells ... healing rune spells ... support rune spells ... summon rune spells. Which...?" +Druid,"instant","spell" -> "... Healing spells ... supply spells ... support spells ... summon spells. Which...?" + +Druid,"attack","rune","spell" -> "... Missile rune spells ... explosive rune spells ... field rune spells ... wall rune spells ... bomb rune spells." +Druid,"healing","rune","spell" -> "In this category ... 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category ... 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category ... 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category ... 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category ... 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category ... 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category ... 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category ... 'Firebomb'." + +Druid,"healing","spell" -> "In this category ... 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category ... 'Food'." +Druid,"support","spell" -> "In this category ... 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category ... 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "... You want 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "... You want 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "... You want 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "... You want 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "... You want 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "... You want 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "... You want 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "... You want 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "... You want 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "... You want 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "... You want 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "... You want 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "... You want 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "... You want 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "... You want 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "... You want 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "... You want 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "... You want 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "... You want 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "... You want 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "... You want 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "... You want 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "... You want 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "... You want 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "... You want 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "... You want 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "... You want 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "... You want 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "... You want 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "... You want 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "... For level 8 ... 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "... For level 9 ... 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "... For level 10 ... 'Antidote'.", Topic=2 +Topic=2,"11$" -> "... For level 11 ... 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "... For level 13 ... 'Great Light'.", Topic=2 +Topic=2,"14$" -> "... For level 14 ... 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "... For level 15 ... 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "... For level 16 ... 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "... For level 17 ... 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "... For level 18 ... 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "... For level 20 ... 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "... For level 23 ... 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "... For level 24 ... 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "... For level 25 ... 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "... For level 27 ... 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "... For level 29 ... 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "... For level 31 ... 'Explosion'.", Topic=2 +Topic=2,"33$" -> "... For level 33 ... 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "... For level 35 ... 'Invisible'.", Topic=2 +Topic=2,"41$" -> "... For level 41 ... 'Energy Wall'.", Topic=2 + +Topic=2 -> "... Only spells for level 8 to 11 ... 13 to 18 ... 20 ... 23 to 25 ... 27 ... 29 ... 31 ... 33 ... 35 and 41...", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "... You already know..." +Topic=3,"yes",Level Amount=SpellLevel(String), "... not level %A..." +Topic=3,"yes",CountMoney "... More money." +Topic=3,"yes" -> "... Here...", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "... Then not." + +} diff --git a/data/npc/snakeeye.npc b/data/npc/snakeeye.npc new file mode 100644 index 0000000..e1dc6c9 --- /dev/null +++ b/data/npc/snakeeye.npc @@ -0,0 +1,80 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# snakeeye.npc: Datenbank für den Wirt im Kriminellencamp + +Name = "Snake Eye" +Outfit = (73,0-0-0-0) +Home = [32657,32190,8] +Radius = 4 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi %N. Come in and have a drink.", Data=3303 +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hang on a second, %N. I'm talking!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,female,! -> "Get lost, stinky dragon." +VANISH,! -> "Bye" + +"bye",female -> "Get lost, stinky dragon.", Idle +"bye",male -> "Bye.", Idle +"job" -> "Well, I'm the boss of this tavern." +"name" -> "I'm Snake Eye." +"eye" -> "Well, I had a dispute with a snake once. And the snake won. Bit my left eye out. Therefore, Snake Eye." +"snake" -> * +"boss" -> "Yeah. I'm the boss. So don't bother me." +"tavern" -> "It's a great tavern. No closing time. No problems with kings or other rulers. Best place in Tibia." +"tibia" -> "There's already too much order in Tibia. We don't need kings or whatever." +"king" -> "We don't need one." +"ruler" -> * +"tibianus" -> "We don't need him." +"time" -> "Go and get a watch." + +"god" -> "The Gods of Tibia! What a crap! It's all superstition!" +"crap" -> "Crap. Crap! CRAP! It's all CRAP!" +"superstition" -> "Believe me! There are no gods." +"durin" -> "He's the worst. The so called god of the dwarfs. I don't believe it. It's all crap." +"steve" -> "Never heard of him." +"guido" -> * +"stephan" -> * +"cip" -> "Cip sux!" + +"thais" -> "In the beginning, it was a nice encampment. Now it's an overcrowded, polluted city. I hate it!" +"carlin" -> "I've never been there. Don't know anything about it." +"kazordoon" -> "Kazordoon is alright. Except the dwarfs. I don't like them. But the mountains are a good place. Been there once." +"ab'dendriel" -> "I've never been there. I don't like the elves anyway." +"edron" -> "That's a place for wealthy toffs!" + +"wild","warrior" -> "There are a lot of wild warriors around. They built this camp." +"camp" -> "Well, the real wild warriors don't live here. They hide in the woods." +"hide" -> "Well. I know of a small camp to the south." +"south" -> "It's abandoned. But I bet that something is hidden there!" +"hidden" -> "Go and find out yourself. You can tell me if you find something." +"copper","key",Count(2970)>0 -> "Hmmm. A copper key. You should ask H.L. about it." +"key", Count(2970)>0 -> * +"h.l." -> "He is a wild warrior. Nobody knows his real name. We just call im H.L. You can find him in the small armory shop." +"hl" -> * +"wood" -> "It's the best place to live. By the way, there's an old wild warrior building to the southwest. It might be interesting for you." +"building" -> "Go and ask H.L. about it." + +"buy" -> "Do you want to eat or drink?" +"eat" -> "Ok, I have fish, meat, and bread. What do you want?" +"drink" -> "I can offer you beer, wine, and water. Water is for free." + +"fish" -> Type=3578, Amount=1, Price=5, "Do you want to buy fish for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=6, "Do you want to buy meat for %P gold?", Topic=1 +"bread" -> Type=3602, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=5, "A beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=6, "Wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=0, "Water is for free. Do you want some?", Topic=1 + +%1,1<%1,"fish" -> Type=3578, Amount=%1, Price=5*%1, "Do you want to buy %A fish for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=6*%1, "Do you want to buy %A pieces of meat for %P gold?", Topic=1 +%1,1<%1,"bread" -> Type=3602, Amount=%1, Price=4*%1, "Do you want to buy a %A breads for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=5*%1, "%A beers for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=6*%1, "%A Wines for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "What? You don't have the money. You crook. Get lost.", Idle +Topic=1 -> "OK, then not." +} diff --git a/data/npc/soullost.npc b/data/npc/soullost.npc new file mode 100644 index 0000000..14fe585 --- /dev/null +++ b/data/npc/soullost.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Lost Soul" +Outfit = (48,0-0-0-0) +Home = [32209,31924,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/soultainted.npc b/data/npc/soultainted.npc new file mode 100644 index 0000000..8697da1 --- /dev/null +++ b/data/npc/soultainted.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Tainted Soul" +Outfit = (48,0-0-0-0) +Home = [32216,31927,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/soultortured.npc b/data/npc/soultortured.npc new file mode 100644 index 0000000..8f1cc8c --- /dev/null +++ b/data/npc/soultortured.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# apparition.npc: Datenbank für einen Geist + +Name = "A Tortured Soul" +Outfit = (48,0-0-0-0) +Home = [32207,31928,12] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/spooky.npc b/data/npc/spooky.npc new file mode 100644 index 0000000..37acf99 --- /dev/null +++ b/data/npc/spooky.npc @@ -0,0 +1,20 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sooky.npc: Datenbank für die Gespensterfrau + +Name = "A Ghostly Woman" +Outfit = (136,0-0-0-0) +Home = [32191,31811,5] +Radius = 7 + +Behaviour = { +ADDRESS,"hello$",! -> NOP +ADDRESS,"hi$",! -> NOP +ADDRESS,! -> Idle +BUSY,"hello$",! -> NOP +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> NOP + +"bye" -> Idle + +} diff --git a/data/npc/stranger.npc b/data/npc/stranger.npc new file mode 100644 index 0000000..8e3f45b --- /dev/null +++ b/data/npc/stranger.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# stranger.npc: Datenbank für den säumigen schuldner david brassacres + +Name = "A Strange Fellow" +Outfit = (128,76-43-38-76) +Home = [32910,32077,9] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Uh? What do you want?!" +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just wait a minute!" +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Still I am better in vanishing!" + + +"bye" -> "Good riddance.", Idle +"farewell" -> * + +"name" -> "My name is not of your concern." +"job" -> "That's only my business, not yours." +QuestValue(229)>1,"david" -> "Yes, yes... Its me .. you exposed me! Stop nagging me with that." +QuestValue(229)>1,"brassacres" -> * +"david" -> "I never heard that name and now get lost." +"brassacres" -> * + +QuestValue(229)=2,"bill" -> Type=3216, Amount=1,"A bill? Oh boy so you are delivering another bill to poor me?",Topic=5 + +Topic=5,"yes",Count(Type)>=Amount -> "Ok, ok, I'll take it. I guess I have no other choice anyways. And now leave me alone in my misery please.",Delete(Type),SetQuestValue(229,3) +Topic=5,"yes",Count(Type) "Ha Ha! You have none!! Naanaanaanaaanaaaa!",Idle +Topic=5 -> "Hoooraaaay! Uhm... I mean, thats fine..." + + +"bill" -> "Thats not my concern, you are probably looking for someone else and now get lost!",Idle + +Topic=1, "hat" -> "Stop bugging me about that hat, do you listen?", Topic=2 + +Topic=2, "hat" -> "Hey! Don't touch that hat! Leave it alone!!! Don't do this!!!!", Topic=3 +Topic=3, "hat" -> "Noooooo! Argh, ok, ok, I guess I can't deny it anymore, I am David Brassacres, the magnificent, so what do you want?",Summon("Rabbit"),Summon("Rabbit"),Summon("Rabbit"),Summon("Rabbit"),SetQuestValue(229,2) +QuestValue(229)=1,"hat" -> "What? My hat?? Theres... nothing special about it!", Topic=1 + +"hat" -> "Get lost!",Idle +} diff --git a/data/npc/stutch.npc b/data/npc/stutch.npc new file mode 100644 index 0000000..d5bdd47 --- /dev/null +++ b/data/npc/stutch.npc @@ -0,0 +1,35 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# stutch.npc: Datenbank für den Wachmann Stutch + +Name = "Stutch" +Outfit = (131,79-79-79-79) +Home = [32310,32170,6] +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,"hi$",! -> "MIND YOUR MANNERS COMMONER! To address the king greet with his title!", Idle +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$","king",! -> "Wait for your audience!" +BUSY,"hail$","king",! -> "Wait for your audience!" +BUSY,"salutations$","king",! -> "Wait for your audience!" +BUSY,"hi$","king",! -> "Wait for your audience!" +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING! You may leave now!", Idle +"farewell" -> * + +"fuck" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"idiot" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"asshole" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"ass$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"fag$" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"stupid" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"tyrant" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"shit" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +"lunatic" -> "Take this!", HP=1, EffectMe(8), EffectOpp(5) +} diff --git a/data/npc/suzy.npc b/data/npc/suzy.npc new file mode 100644 index 0000000..d0a38a1 --- /dev/null +++ b/data/npc/suzy.npc @@ -0,0 +1,25 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# suzy.npc: Datenbank für die Bankangestellte Suzy + +Name = "Suzy" +Outfit = (136,78-10-96-95) +Home = [32320,32258,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N! What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I work in this bank. I can change money for you." +"name" -> "I am Suzy." +"time" -> "It is exactly %T." + +@"gen-bank.ndb" +} diff --git a/data/npc/sylvester.npc b/data/npc/sylvester.npc new file mode 100644 index 0000000..457606d --- /dev/null +++ b/data/npc/sylvester.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# sylvester.npc: Datenbank für eine Stadtwache in Venore + +Name = "Sylvester" +Outfit = (131,113-113-113-115) +Home = [32897,32145,4] +Radius = 3 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/talesia.npc b/data/npc/talesia.npc new file mode 100644 index 0000000..bf0cc23 --- /dev/null +++ b/data/npc/talesia.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Talesia.npc: Datenbank für die Besitzerin des Grünzeugmarktes Talesia + +Name = "Talesia" +Outfit = (138,114-78-77-116) +Home = [32979,32035,5] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutation, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Come back later, I am talking." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Fare thee well." + +"bye" -> "Fare thee well.", Idle +"name" -> "I am Talesia De'Mir, owner of Crunor's Finest Warehouse." +"job" -> "I am a the owner of Crunor's Finest Warehouse of course." +"time" -> "It's %T right now." +"king" -> "We pay this man enough to live here undisturbed of major interventions." +"tibianus" -> * +"army" -> "At least they are useful, but we pay enough taxes to supply the entire Thaian army." +"ferumbras" -> "I hope he is aware that his enemies live elsewhere." +"excalibug" -> "I am contend with my familysword meloncutter." +"thais" -> "I hope they live well from our taxes..." +"tibia" -> "The world is a treasure chest for those of knowledge and skill." +"warehouse" -> "My warehouse is only one of many. We merchants hold this city together and lead it to prosperity." +"carlin" -> "They are a bit problematic as business partners, but their independence from Thais is... interesting." +"news" -> "Even bad news can be good news, if you play your cards well." +"tax" -> "Venore is the major tax payer in the whole realm. So we more than deserve the privileges the king granted us." +"crunor" -> "A god worth to worship. At least he gives something useful back to the faithful." +"privilege" -> "We are alowed to trade with anyone, Thaian subject or not, have no Thaian noble as governor, and own the exclusive gambling license." +"gambling" -> "I don't care much about it, though others profit greatly." +} diff --git a/data/npc/talphion.npc b/data/npc/talphion.npc new file mode 100644 index 0000000..5aa7fe6 --- /dev/null +++ b/data/npc/talphion.npc @@ -0,0 +1,102 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# talphion.npc: Datenbank fuer den Technomancer Talphion (Zwergenstadt) + +Name = "Talphion" +Outfit = (160,11-86-87-106) +Home = [32563,31894,12] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "HIHOOOO %N! " +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "NOT NOW! TALKING! STAND IN LINE!.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "HEY! CAREFUL WHERE YOU STEP!" + +"bye" -> "YEAH, GO AWAY!", Idle +"farewell" -> * +"job" -> "WHERE SHOULD I HOP?", topic=1 +topic=1,"job" -> "OH, JOB? I AM THE CHIEF TECHNOMANCER!" +"name" -> "I HAVE NO TIME FOR A GAME!", topic=2 +topic=2,"name" -> "I AM TALPHION SPARKBENDER, SON OF THE MACHINE, FROM THE SAVAGE AXES." +"hall","ancients" -> "JUST A BUNCH OF BONES." +"tibia" -> "CAN'T TELL MUCH ABOUT IT. SELDOM GET OUT HERE, I AM A BUSY DWARF." +"kazordoon" -> "WHAT?", topic=3 +topic=3,"kazordoon" -> "WHOS DOOMED?", topic=4 +topic=4,"kazordoon" -> "OH, THE CITY? NICE, ISN'T IT?" + +"big","old" -> "THIS IS THE NAME OF THIS MOUNTAIN!" +"elves" -> "NO. I DON'T NEED ANY SHELVES!" +"humans" -> "A PROMISING RACE, SOME OF THEM ACTUALLY ADMIRE MECHANICS." +"orcs" -> "LET THEM COME, I AM WORKING ON A LITTLE SURPRISE FOR THEM! " +"minotaurs" -> * +"pyromancer" -> "OLD FOOLS, TO MUCH CONCERNED ABOUT TRADITION." +"geomancer" -> * +"technomancer" -> "WE ARE THE FUTURE. WE WILL BECOME A MAJOR POWER IN DWARFEN SOCIETY SOON! THEY WILL SEE, THEY WILL ALL SEE! " +"god" -> "GODS, WHO NEEDS GODS, WHEN WE CAN BUILD THE CORRECT MACHINE FOR EVERY OCCASION?" +"fire" -> "NICE RESOURCE FOR OUR MACHINES, BUT NO NEED TO MAKE A BIG DEAL ABOUT IT, JAWOLL!" +"flame" -> * +"earth" -> "SORRY, BUT JUST DUST AND MUD TO ME." +"durin" -> "I AM SURE HE WOULD BE SMART ENOUGH TO SEE THE CHANCES WE PROVIDE FOR DWARFENHOOD." +"life" -> "WHAT HIVE?" +"plant" -> "HEY! HOW DID YOU LEARN ABOUT OUR SECRET PLANT?" +"citizen" -> "YOU CAN BECOME CITIZEN IN THE HALL OF ANCIENTS." +"kroox" -> "WE COULD TEACH HIM MUCH IF HE LISTENED." +"jimbin" -> "HIS BREWERY SAVED OUR DAY MORE THEN ONCE IN MANY WAYS." +"maryza" -> "LOVELY, BUT PREDJUDICED AS MOST DWARFS ARE." +"bezil" -> "BEZIL AND NEZIL ARE RUNNING A SHOP." +"nezil" -> * +"uzgod" -> "WE COULD MAKE FOR HIM MACHINES TO DO HIS WORK IN HALF THE TIME I BET." +"etzel" -> "WHO NEEDS MAGIC? PAH!" +"duria" -> "KNIGHTS DO NOT HAVE THE BRAIN TO EVEN UNDERSTAND WHAT WE ARE OFFERING THEM." +"offering" -> "YES, THE MOST SOPHISTICATED ITEMS THEY BUY ARE CROSSBOWS." + +"crossbow" -> Type=3349, Amount=1, Price=1150, "DO YOU WANT TO BUY A CROSSBOW FOR %P GOLD?", Topic=5 +"bolt" -> Type=3446, Amount=1, Price=5, "DO YOU WANT TO BUY A BOLT FOR %P GOLD?", Topic=5 + +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=5*%1, "DO YOU WANT TO BUY %A BOLTS FOR %P GOLD?", Topic=5 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=1150*%1, "DO YOU WANT TO BUY %A CROSSBOWS FOR %P GOLD?", Topic=5 + +Topic=5,"yes",CountMoney>=Price -> "HERE YOU ARE.", DeleteMoney, Create(Type) +Topic=5,"yes" -> "NOT ENOUGH MONEY. SORRY." +Topic=5 -> "PERHAPS NEXT TIME." + +"emperor" -> "AT LEAST HES SMART ENOUGH TO LEAVE US ALONE, SO THERES HOPE FOR HIM." +"kruzak" -> * +"motos" -> "STUPID IDIOT, WITH SOME MORE RESOURCES I COULD BUILD FOR HIM WARMACHINES BEYOND HIS WILDEST DREAMS! " +"general" -> * +"army" -> "ONE DAY OUR MACHINES WILL CHANGE THE ARMY STRUCTURES DRASTICALLY, JAWOLL!" +"ferumbras" -> "I BET I COULD BUILD A MACHINE TO SHRED HIM INTO PIECES!" +"excalibug" -> "OLD FASHIONED BUTTERKNIFE! IF THEY LET ME, I WOULD CREATE WEAPONS THAT LEVEL ENTIRE CITIES!" +"news" -> "ASK JIMBIN ABOUT HIS BREWS, NOT ME!" +"monster" -> "I COULDN'T CARE LESS ABOUT THEM." +"help" -> "WHOM YOU ARE CALLING A WHELP, YOU &$(&*#!", idle +"quest" -> "BRING ME THE SCREWDRIVER OF KURIK AND I WILL REWARD YOU WITH A STEAMPOWERED SPIKESWORD!" +"task" -> * +"what","do" -> * +"gold" -> "DONATIONS ARE ALWAYS WELCOME!" +"money" -> * +"equipment" -> "YOU ARE TOO STUPID FOR MOST OF OUR STUFF, BUT I COULD SELL YOU SOME CROSSBOWS." +"fight" -> "NO, DONT SWITCH OUT THE LIGHT." + +Topic=6,"dress","pattern" -> "A PRESS LANTERN? NEVER HEARD ABOUT IT!",Topic=7 +Topic=7,"dress","pattern" -> "CHESS? I DONT PLAY CHESS!",Topic=8 +Topic=8,"dress","pattern" -> "A PATTERN IN THIS MESS?? HEY DON'T INSULT MY MACHINEHALL!",Topic=9 + +Topic=9,"dress","pattern" -> "AH YES! I WORKED ON THE DRESS PATTERN FOR THOSE UNIFORMS. STAINLESS TROUSERS, STEAM DRIVEN BOOTS! ANOTHER MARVEL TO BEHOLD! I'LL SEND A COPY TO KEVIN IMEDIATELY!",SetQuestValue(233,4) +Topic=9,"uniform" -> * + +"technical","details" -> "TECH DETAILS ABOUT WHAT???" +"dress","pattern",QuestValue(233)=3 -> "DRESS FLATTEN? WHO WANTS ME TO FLATTEN A DRESS?",Topic=6 + +"dress","pattern",QuestValue(233)<>3 -> "DRESS FLATTEN? WHO WANTS ME TO FLATTEN A DRESS?" +"uniform" -> "NO, HERE IS NO UNICORN!" + +"heal",Burning>0 -> "YOU ARE BURNING! THAT'S FUN, HOW DO YOU DO THAT?" +"heal",Poison>0 -> "YOU ARE POISONED! HAVE YOU DRUNK THE STUFF IN A GREEN BOTTLE? THAT'S SUPERGLUE, NOT SUPPER-GLUE, STUPID!" +"heal" -> "I AM AN ENGINEER, NOT A DOCTOR!" +"time" -> "ONE DAY I WILL CREATE A CLOCK FOR THE COLOSSUS" +"colossus" -> "NICE PIECE OF WORK. WOULD BE MORE FUN IF IT COULD MOVE AROUND... WE HAVE PLANS..." +} diff --git a/data/npc/tandros.npc b/data/npc/tandros.npc new file mode 100644 index 0000000..4bab7f4 --- /dev/null +++ b/data/npc/tandros.npc @@ -0,0 +1,87 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tandros.npc: Datenbank für den magierhändler Tandros + +Name = "Tandros" +Outfit = (132,78-79-113-95) +Home = [32621,32739,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, student of the arcane." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Show some patience please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"farewell" -> * +"job" -> "I am selling items of magic power such as runes, wands, rods, life fluids and mana fluids." +"name" -> "I am Tandros the magnificent." +"time" -> "It is a crime against the order of things to try measuring time. The very thought of squeezing the majesty of centuries and centuries into a puny mechanical device is blasphemy." +"king" -> "Our king is a worldly one. But if you buy enough of my fluids and runes you might become the king of magic one day." +"venore" -> "Technically I am an employee of those trade barons of Venore but of course no one can control my magnificent mind." +"thais" -> "It is so crowded and people there are always busy. I dare to say that this is a city that has lost its magic at some point." +"carlin" -> "I heard there are many druids that are quite influential. They should know how to keep the magic of a place alive. I am looking forward to travel there one day." +"edron" -> "Edron is rumoured to be a place of ancient mysteries and powerful magic." +"jungle" -> "The magic is out there somewhere." + +"tibia" -> "The world is full of magic that is waiting to be used ... perhaps by you! Take the first step by buying my wares to gather even more magic power for yourself." + +"kazordoon" -> "Dwarves have little love for magic. That makes them quite suspicious, doesn't it?" +"dwarves" -> * +"dwarfs" -> * +"ab'dendriel" -> "Elves are such marvelous, mythic creatures. They are full of magic." +"elves" -> * +"elfs" -> * +"darama" -> "Although our people, spoken in cosmological terms, have setteled here just recently, there is already much history hidden here. ...", + "Not only mysteries and magical secrets but also many treasures are here to be explored by that person that is equipped with enough runes and fluids to master all dangers." +"darashia" -> "An unremarkable little town, but riding there by carpet is pure magic." +"ankrahmun" -> "A city that breathes evil and dark magic. Stay away or be at least well prepared if you intend to visit the city of the dead." +"ferumbras" -> "He might be evil, but his powers are unimaginable! To stand a chance against evil overlords like him, you have to buy loads of my runes and fluids." +"excalibug" -> "A weapon of unparalleled magic. Don't listen to people that tell you that this is only a myth. It might be a dream but remember, dreams can come true." +"apes" -> "They are attacking travellers and even our settlement now and then. What can be a better way for you to survive than by preparing yourself well and to buy enough fluids and runes?" +"lizard" -> "The lizards live far away on the other side of the dangerous jungle. If you want to go there to learn more about their secrets, I strongly advise you to supply yourself with runes and fluids." +"dworcs" -> "The dworcs are fierce enemies and the poison they use is lethal. If you don't have some fluids and runes with you, you are easy prey to them." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + + +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on every vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-prem-s.ndb" +} diff --git a/data/npc/tesha.npc b/data/npc/tesha.npc new file mode 100644 index 0000000..23a8e67 --- /dev/null +++ b/data/npc/tesha.npc @@ -0,0 +1,121 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tesha.npc: Datenbank für die pyramidenhändlerin Tesha + +Name = "Tesha" +Outfit = (140,77-32-81-93) +Home = [33135,32821,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "May enlightenment be your path." + +"bye" -> "May enlightenment be your path.", Idle +"farewell" -> * +"job" -> "I sell and buy gems and jewelry." +"name" -> "I am the mourned Tesha." +"time" -> "Time is yet another burden that lies heavy on our mortal bodies." +"temple" -> "The temple can offer us guidance and solace in our mortal existence." +"pharaoh" -> "He is the benevolent father of this nation. Blessed be our saviour." +"oldpharaoh" -> "This poor man could not comprehend his son's wisdom. Perhaps he has spelled his own eternal doom." +"scarab" -> "The priests say they are sacred beings, although ... I find them scary!" +"chosen" -> "I can only hope my humble work for our community and for the temple will make me worthy one day to be elevated to the rank of a chosen one. One to whom the path of ascension is opened up through undeath." + +"tibia" -> "The world is so huge, and I have seen so little. Perhaps if I am chosen one day I will travel and see it all." +"carlin" -> "Those citites are so far away. So far that the enlightened preachings of our divine pharaoh cannot reach those poor misguided souls." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "Dwarves have a strong Akh. This makes them arrogant and deaf to the true creed." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Most elves lack the sincerity to strive for ascension. At least that's what the priests are telling us." +"elves" -> * +"elfes" -> * +"darama" -> "In the desert the lines of life and death are clearly drawn. Because of this it is easier for us, its children, to focus on them. In the jungle those lines are fuzzy and blurred, and people easily fall victim to temptation." +"darashia" -> "Those poor souls there might still be saved if only they listened." +"daraman" -> "He was a great man. If he had left his mortal existence behind he might have become one of the greatest prophets of the true faith, second only to the pharaoh himself." +"Ankrahmun" -> "This city is both a refuge and centre of learning for the believers of the true faith taught by his divine majesty the pharaoh." + +"pharaoh" -> "The pharaoh has such amazing patience with us puny mortals. He is truly a caring father of this nation." +"mortality" -> "Mortality can be overcome. It is a sickness, but it can be cured through undeath." +"false", "gods" -> "These greedy beings are trying to devour us all. May the pharaoh thwart their evil plans and free us from their reign of terror!" +"ascension" -> "Oh, I am not asking for much, you know. I mean, I really don't have to be a god or something. All I wish for is a bit of the wisdom that comes with ascension." +"Akh'rah","Uthun" -> "I don't really understand this concept, but from what I know it is the three components that make up every being." +"Akh" -> "Our body. The only physical part of the Akh'rah Uthun." + +"undead" -> "Undeath is the reward for a life of faith and service." +"undeath" -> * +"Rah" -> "The Rah is our essence. The spiritual bond that keep the other parts of the Akh'rah Uthun together." +"uthun" -> "The Uthun is all we learned in life." +"mourn" -> "The dead mourn our tempted existence, and we mourn ourselves." +"arena" -> "Look for it in the eastern part of the city." +"palace" -> "Isn't the palace magnificent to behold? It is so impressive!" + +"offer" -> "I can offer you various gems, pearls or some wonderful jewels. I also change money." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"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." +"talon" -> "I don't trade or work with these magic gems. It's better you ask a mage about this." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/data/npc/tezila.npc b/data/npc/tezila.npc new file mode 100644 index 0000000..efeac28 --- /dev/null +++ b/data/npc/tezila.npc @@ -0,0 +1,81 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tezila.npc: Datenbank für die Juwelierin Tezila + +Name = "Tezila" +Outfit = (160,3-92-110-115) +Home = [32657,31922,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho, %N. Come in and look. But don't touch the exhibits, jawoll!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy with a customer. Please have some patience.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am a jeweller. Maybe you want to have a look at my wonderful offers. I also exchange money." +"name" -> "I am Tezila Gemcutter, daughter of Fire, from the Savage Axes." +"time" -> "It's %T." + +"offer" -> "I can sell you glittering gems, precious pearls or some ... uhm ... jolly jewels." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"gem" -> "You can buy and sell small diamonds, sapphires, rubies, emeralds, and amethysts." +"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." + +"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 +"ruby","necklace" -> Type=3016, Amount=1, Price=3560, "Do you want to buy a ruby necklace for %P gold?", Topic=1 +"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 +"small","diamond" -> Type=3028, Amount=1, Price=600, "Do you want to buy a small diamond for %P gold?", Topic=1 +"small","sapphire" -> Type=3029, Amount=1, Price=500, "Do you want to buy a small sapphire for %P gold?", Topic=1 +"small","ruby" -> Type=3030, Amount=1, Price=500, "Do you want to buy a small ruby for %P gold?", Topic=1 +"small","emerald" -> Type=3032, Amount=1, Price=500, "Do you want to buy a small emerald for %P gold?", Topic=1 +"small","amethyst" -> Type=3033, Amount=1, Price=400, "Do you want to buy a small amethyst for %P gold?", Topic=1 + +%1,1<%1,"wedding","ring" -> Type=3004, Amount=%1, Price=990*%1, "Do you want to buy %A wedding rings for %P gold?", Topic=1 +%1,1<%1,"golden","amulet" -> Type=3013, Amount=%1, Price=6600*%1, "Do you want to buy %A golden amulets for %P gold?", Topic=1 +%1,1<%1,"ruby","necklace" -> Type=3016, Amount=%1, Price=3560*%1, "Do you want to buy %A ruby necklaces for %P gold?", Topic=1 +%1,1<%1,"white","pearl" -> Type=3026, Amount=%1, Price=320*%1, "Do you want to buy %A white pearls for %P gold?", Topic=1 +%1,1<%1,"black","pearl" -> Type=3027, Amount=%1, Price=560*%1, "Do you want to buy %A black pearls for %P gold?", Topic=1 +%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=600*%1, "Do you want to buy %A small diamonds for %P gold?", Topic=1 +%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=500*%1, "Do you want to buy %A small sapphires for %P gold?", Topic=1 +%1,1<%1,"small","rub" -> Type=3030, Amount=%1, Price=500*%1, "Do you want to buy %A small rubies for %P gold?", Topic=1 +%1,1<%1,"small","emerald" -> Type=3032, Amount=%1, Price=500*%1, "Do you want to buy %A small emeralds for %P gold?", Topic=1 +%1,1<%1,"small","amethyst" -> Type=3033, Amount=%1, Price=400*%1, "Do you want to buy %A small amethysts for %P gold?", Topic=1 + +"sell","white","pearl" -> Type=3026, Amount=1, Price=160, "Do you want to sell a white pearl for %P gold?", Topic=2 +"sell","black","pearl" -> Type=3027, Amount=1, Price=280, "Do you want to sell a black pearl for %P gold?", Topic=2 +"sell","small","diamond" -> Type=3028, Amount=1, Price=300, "Do you want to sell a small diamond for %P gold?", Topic=2 +"sell","small","sapphire" -> Type=3029, Amount=1, Price=250, "Do you want to sell a small sapphire for %P gold?", Topic=2 +"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",%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 +"sell",%1,1<%1,"small","diamond" -> Type=3028, Amount=%1, Price=300*%1, "Do you want to sell %A small diamonds for %P gold?", Topic=2 +"sell",%1,1<%1,"small","sapphire" -> Type=3029, Amount=%1, Price=250*%1, "Do you want to sell %A small sapphires for %P gold?", Topic=2 +"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 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have that many." +Topic=2 -> "Maybe next time." + +@"gen-bank.ndb" +} diff --git a/data/npc/thanita.npc b/data/npc/thanita.npc new file mode 100644 index 0000000..3b68444 --- /dev/null +++ b/data/npc/thanita.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Thanita.npc: Datenbank für die Amazonenwächterin des Turms + +Name = "Thanita" +Outfit = (139,78-52-64-52) +Home = [32535,31773,1] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "How could you sneak up on me like this? I thought you were one of THEM! Well, since you are not, what brings you to this wilderness?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please wait.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Take good care of yourself traveler!" + +"bye" -> "Take good care of yourself traveler. Would be a shame to lose such a courageous wanderer to those green monsters.", Idle +"farewell" -> * +"job" -> "I'm an amazon guard. It's my job to keep my eyes open and to keep enemies from passing by. My job here truely is one of the toughest. All because of these nerve-racking beasts." +"name" -> "My name is Thanita. Nice to meet you." + +"beasts" -> "These green, orcish raiders come in masses. Hundreds of them. They are worse than those goblins I have to deal with from time to time. ...", + "Some of them come on beastly warwolves, others shoot fireballs at you and some are just plain ugly. ...", + "They have never succeeded though in capturing this tower. So far I have always been able to put them to flight." +"orcs" -> * +"them" -> * +"raid" -> * + + +"mission" -> "Well, I cannot provide you with a mission, I have a mission to fulfill myself. ...", + "However, when the orcs attack, you are more than welcome to help me to defeat them. ...", + "I'll even let you have all the stuff they carry with them. Even if they looted it from the tower here." +"quest" -> * + + +"fight" -> "To get rid of them, you need to be quite good in different martial arts." +"defeat" -> * + +"femor", "hills" -> "Are you kidding me? This is femor hills. Probably one of the roughest areas in Tibia. There is nobody else could do my job." + +"tower" -> "This is a watchtower of the city of carlin. From here I can see pretty much all of the surrounding lands. Hardly anybody can startle me up here. I see all enemies long before they can see me." + +"enemies" -> "The enemies I fear most here are these nasty orcs." + +"amazon" -> "I see you have heard of amazons before. Well let me tell you, probably everything you heard is true. We are much stronger and tougher than people think. Also, we know how to fight and could teach many men how to handle a weapon. ...", + "Not that we would do such a foolish thing." + +"bye" -> "Take good care of yourself traveler. Would be a shame to lose such a courageous wanderer to those green monsters." +} diff --git a/data/npc/thomas.npc b/data/npc/thomas.npc new file mode 100644 index 0000000..cdc8b9d --- /dev/null +++ b/data/npc/thomas.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# thomas.npc: Datenbank für den Schreibwarenhändler Thomas + +Name = "Thomas" +Outfit = (128,116-11-101-76) +Home = [33256,31838,4] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Feel welcome, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Don't be that impatient, %N. Wait a moment!", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"name" -> "Thomas." +"job" -> "Selling spellbooks, notebooks, scrolls, documents, parchments, inkwells, and the like." +"time" -> "No idea." +"king" -> "Know nothing interesting about him." +"tibianus" -> * +"army" -> "Ask in the castle." +"ferumbras" -> "Only heard of him." +"excalibug" -> "No idea." +"thais" -> "Never been there." +"tibia" -> "Like it here best." +"carlin" -> "Never been there." +"edron" -> "This town." +"news" -> "Nothing interesting." +"rumors" -> * + +"offer" -> "Selling scrolls, documents, parchments, spellbooks, books, and inkwells." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"book" -> "I offer different kind of books: brown, black and small books. Which book do you want?" + +"scroll" -> Type=2815, Amount=1, Price=5, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 +"brown","book" -> Type=2837, Amount=1, Price=15, "Do you want to buy a brown book for %P gold?", Topic=1 +"black","book" -> Type=2838, Amount=1, Price=15, "Do you want to buy a black book for %P gold?", Topic=1 +"small","book" -> Type=2839, Amount=1, Price=15, "Do you want to buy a small book for %P gold?", Topic=1 +"inkwell" -> Type=3509, Amount=1, Price=8, "Do you want to buy an inkwell for %P gold?", Topic=1 + +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=5*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 +%1,1<%1,"brown","book" -> Type=2837, Amount=%1, Price=15*%1, "Do you want to buy %A brown books for %P gold?", Topic=1 +%1,1<%1,"black","book" -> Type=2838, Amount=%1, Price=15*%1, "Do you want to buy %A black books for %P gold?", Topic=1 +%1,1<%1,"small","book" -> Type=2839, Amount=%1, Price=15*%1, "Do you want to buy %A small books for %P gold?", Topic=1 +%1,1<%1,"inkwell" -> Type=3509, Amount=%1, Price=8*%1, "Do you want to buy %A inkwells for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/tibra.npc b/data/npc/tibra.npc new file mode 100644 index 0000000..573c843 --- /dev/null +++ b/data/npc/tibra.npc @@ -0,0 +1,112 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tibra.npc: Datenbank für die Priesterin Tibra + +Name = "Tibra" +Outfit = (138,60-92-90-95) +Home = [32318,31783,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$","tibra",! -> "Welcome in the name of the gods, pilgrim %N!" +ADDRESS,"hi$","tibra",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +ADDRESS,male,"my$","heart$","belongs","to",! -> "I ask thee, %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=9 +ADDRESS,female,"my$","heart$","belongs","to",! -> "I ask thee, %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=9 + +BUSY,"hello$",! -> "%N, please be patient and wait a moment, my child.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "May the gods guard you!" + +"bye" -> "Good bye, %N. May the gods be with you to guard and guide you, my child!", Idle +"farewell" -> * +"job" -> "I am a priest of the great pantheon." +"news" -> "Sorry, worldly matters are of no concern to me." +"name" -> "My name is Tibra. Your soul tells me that you are %N" +"tibia" -> "The world of Tibia is the creation of the gods." +"how","are","you"-> "Thank you, I'm fine, the gods give me hope and comfort." +"sell" -> "The grace of the gods must be earned, it cannot be bought!" +"sin$" -> "Do you wish to confess your sins, my chilid?", Topic=3 +"sins$" -> * + +"marriage","ceremony" -> "So you want me to initate a marriage ceremony?", Topic=5 +Topic=5,"yes" -> "In the name of the Gods of good, I ask thee, if both of you are prepared and ready!", Topic=6 +Topic=5,"i$","will$" -> * +Topic=5 -> "Perhaps another time, marriage isn't a step one should consider without love in the heart." +Topic=6,"yes" -> "Silence, please! I hereby invoke the attention of the eternal Powers looking over our souls and lives. May the gods bless us!", EffectMe(2), Topic=7 +Topic=6,"i$","will$" -> * +Topic=7,male,"may","gods","bless","us" -> "I ask thee %N, will you honor your bride and stay at her side even in the darkest hours live could bring upon you?", Topic=8 +Topic=7,female,"may","gods","bless","us" -> "I ask thee %N, will you honor your groom and stay at his side even in the darkest hours live could bring upon you?", Topic=8 +Topic=8,male,"yes" -> "So by the powers of the gods your soul is now bound to your bride. Bride, step forward and tell me to whom your heart belongs!", EffectOpp(1), idle +Topic=8,"i$","will$" -> * +Topic=8,female,"yes" -> "So by the powers of the gods your soul is now bound to your groom. Groom, step forward and tell me to whom your heart belongs!", EffectOpp(1), idle +Topic=8,"i$","will$" -> * +Topic=9,"yes" -> "So by the powers of the gods your souls are now bound together for eternity. May the gods watch with grace over your further life as married couple! Go now and celebrate your marriage!", EffectOpp(1), EffectMe(7), Idle +Topic=9,"i$","will$" -> * +Topic=9,"no" -> "Your neglection of love hurts my heart. Leave now!", idle + +"god$" -> "The gods of good guard us and guide us, the gods of evil want to destroy us and steal our souls!", Topic=2 +"gods$" -> * +"life" -> "The teachings of Crunor tell us to honor life and not to harm it." +"citizen" -> "The things we priests know about the citizens are confidential." +"lugri" -> "Only a man can fall as low as he did. His soul rotted away already." +"queen" -> "Queen Eloise is wise to listen to the proposals of the druidic followers of Crunor." +"monster" -> "Remind: Not everything you call monster is evil to the core!" +"quest" -> "It is my mission to bring the teachings of the gods to everyone." +"mission" -> * +"gold" -> Price=15, "Do you want to make a donation?", Topic=1 +"money" -> * +"donation" -> * +"fight" -> "It is MY mission to teach, it is YOUR mission to fight!" +"slay" -> * +"mission" -> * +"heal$",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"heal$",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"heal$" -> "You aren't looking that bad. Sorry, I need my powers for cases more severe than yours." +"help",HP<40 -> "You are hurt, my child. I will heal your wounds.", HP=40, EffectOpp(13) +"help",Poison>0 -> "You are poisoned, my child. I will help you.", Poison(0,0), EffectOpp(14) +"help",Burning>0 -> "You are burning, my child. I will help you.", Burning(0,0), EffectOpp(15) +"help" -> "You aren't looking that bad. Sorry, I need my powers for cases more severe than yours." +"ferumbras" -> "The fallen one should be mourned, not feared." +"time" -> "Now, it is %T." +"excalibug" -> "The mythical blade was hidden in ancient times. Its said that powerful wards protect it." +"graveyard" -> "There's something strange in its neighbourhood. But whom we gonna call for help if not the gods?" +"crypt" -> * +"mausoleum" -> * + +Topic=2,"good" -> "The gods we call the good ones are Fardos, Uman, the Elements, Suon, Crunor, Nornur, Bastesh, Kirok, Toth, and Banor." +"fardos" -> "Fardos is the creator. The great obsever. He is our caretaker." +"uman" -> "Uman is the positive aspect of magic. He brings us the secrets of the arcane arts." +"suon" -> "Suon is the lifebringing sun. He observes the creation with love." +"crunor" -> "Crunor, the great tree, is the father of all plantlife. He is a prominent god for many druids." +"nornur" -> "Nornur is the mysterious god of fate. Who knows if he is its creator or just a chronist?" +"bastesh" -> "Bastesh, the deep one, is the goddess of the sea and its creatures." +"kirok" -> "Kirok, the mad one, is the god of scientists and jesters." +"toth" -> "Toth, Lord of Death, is the keeper of the souls, the guardian of the afterlife." +"banor" -> "Banor, the heavenly warrior, is the patron of all fighters against evil. He is the gift of the gods to inspire humanity." +"tibiasula" -> "Tibiasula lost her life, but out of her essence the world was created." +Topic=2,"tibia" -> "Tibia is the essence of the elemental power of earth." +"sula" -> "Sula is the essence of the elemental power of water." +"air" -> "Air is one of the primal elemental forces, sometimes worshipped by tribal shamans." +"fire" -> "Fire is one of the primal elemental forces, sometimes worshipped by tribal shamans." + +Topic=2,"evil" -> "The gods we call the evil ones are Zathroth, Fafnar, Brog, Urgith, and the Archdemons!" +"zathroth" -> "Zathroth is the destructive aspect of magic. He is the deciver and the thief of souls." +"fafnar" -> "Fafnar is the scorching sun. She observes the creation with hate and jealousy." +"brog" -> "Brog, the raging one, is the great destroyer. The berserk of darkness." +"urgith" -> "The bonemaster Urgith is the lord of the undead and keeper of the damned souls." +"archdemons" -> "The demons are followers of Zathroth. The cruelest are known as the ruthless seven." +"ruthless","seven" -> "I dont want to talk about that subject!" + +Topic=1,"no" -> "As you wish." +Topic=1,"yes",CountMoney>=Price -> "May the gods bless you!", DeleteMoney, EffectOpp(15) +Topic=1,"yes",CountMoney "Don't be ashamed, but you lack the gold." + +Topic=3,"yes" -> "So tell me, what shadows your soul, my child.", Topic=4 +Topic=3 -> "As you wish." +Topic=4 -> "Meditate on that and pray for your soul." +} diff --git a/data/npc/tim.npc b/data/npc/tim.npc new file mode 100644 index 0000000..d969dd6 --- /dev/null +++ b/data/npc/tim.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tim.npc: Datenbank für die Stadtwache am Osttor + +Name = "Tim, the guard" +Outfit = (131,19-19-19-19) +Home = [32428,32226,6] +Radius = 4 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/data/npc/timur.npc b/data/npc/timur.npc new file mode 100644 index 0000000..95d7138 --- /dev/null +++ b/data/npc/timur.npc @@ -0,0 +1,91 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# timur.npc: Datenbank für den Händler Timur auf der Insel Fibula + +Name = "Timur" +Outfit = (128,0-116-102-95) +Home = [32191,32443,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$","timur",! -> "Hello, %N. Come in and buy." +ADDRESS,"hi$","timur",! -> * +ADDRESS,"hello$",! -> "Welcome to my little shop, adventurer! First read my blackboards." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry, I am already talking to a customer, %N. Wait, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye, bye." + +"bye" -> "Bye, bye.", Idle +"farewell" -> * +"job" -> "I am selling equipment. Do you want something?" +"name" -> "I am Timur. Sorry, I have not much equipment for sale. The business is running low." +"timur" -> * +"time" -> "I am sorry, I have no watch." +"fibula" -> "It's a very nice isle. But we don't have enough weapons to defeat the many wolves." +"wolf" -> "They are everywhere around the village." +"wolves" -> * +"helmet" -> "I can sell you a viking helmet in a very good quality." +"food" -> "If you are looking for food, buy a rod and go fishing." +"weapon" -> "At the moment I have no weapons to offer. Weapons are very rare on this isle, so I have to buy a few." +"bow" -> "We have too few bows on this isle for our hunters." +"crossbow" -> "We have too few crossbows on this isle for our hunters." +"fluid" -> "The magic shops have a monopole on fluids now... argl!" +"magic" -> * + +"equipment" -> "I sell torches, scrolls, documents, parchments, ropes, fishing rods, sixpacks of worms, arrows, bolts, and a nice helmet." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"ammo" -> "I have arrows and bolts in this shop." +"ammunition" -> * + +"torch" -> Type=2920, Amount=1, Price=3, "Do you want to buy a torch for %P gold?", Topic=1 +"scroll" -> Type=2815, Amount=1, Price=10, "Do you want to buy a scroll for %P gold?", Topic=1 +"document" -> Type=2834, Amount=1, Price=12, "Do you want to buy a document for %P gold?", Topic=1 +"parchment" -> Type=2835, Amount=1, Price=8, "Do you want to buy a parchment for %P gold?", Topic=1 +"rope" -> Type=3003, Amount=1, Price=65, "Do you want to buy a rope for %P gold?", Topic=1 +"rod" -> Type=3483, Amount=1, Price=170, "Do you want to buy a fishing rod for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=3, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=4, "Do you want to buy a bolt for %P gold?", Topic=1 +"viking","helmet" -> Type=3367, Amount=1, Price=265, "Do you want to buy a viking helmet for %P gold?", Topic=1 + +%1,1<%1,"torch" -> Type=2920, Amount=%1, Price=3*%1, "Do you want to buy %A torches for %P gold?", Topic=1 +%1,1<%1,"scroll" -> Type=2815, Amount=%1, Price=10*%1, "Do you want to buy %A scrolls for %P gold?", Topic=1 +%1,1<%1,"document" -> Type=2834, Amount=%1, Price=12*%1, "Do you want to buy %A documents for %P gold?", Topic=1 +%1,1<%1,"parchment" -> Type=2835, Amount=%1, Price=8*%1, "Do you want to buy %A parchments for %P gold?", Topic=1 +%1,1<%1,"rope" -> Type=3003, Amount=%1, Price=65*%1, "Do you want to buy %A ropes for %P gold?", Topic=1 +%1,1<%1,"rod" -> Type=3483, Amount=%1, Price=170*%1, "Do you want to buy %A fishing rods for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=3*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=4*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 +%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=265*%1, "Do you want to buy %A viking helmets for %P gold?", Topic=1 + +"worm" -> "I sell worms only in sixpacks for 5 gold each, how many sixpacks of worms do you want to buy?" +"worms" -> * +%1,"worms" -> Type=3492, Amount=%1*6, Price=5*%1, "So you want to buy some of my sixpacks with altogether %A worms for %P gold?", Topic=1 +%1,"six","pack" -> * + + +"sell","bow" -> Type=3350, Amount=1, Price=130, "Do you want to sell a bow for %P gold?", Topic=2 +"sell","crossbow" -> Type=3349, Amount=1, Price=160, "Do you want to sell a crossbow for %P gold?", Topic=2 +"sell","viking","helmet" -> Type=3367, Amount=1, Price=66, "Do you want to sell a viking helmet for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","hatchet" -> Type=3276, Amount=1, Price=25, "Do you want to sell a hatchet for %P gold?", Topic=2 + +"sell",%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=130*%1, "Do you want to sell %A bows for %P gold?", Topic=2 +"sell",%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=160*%1, "Do you want to sell %A crossbows for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","helmet" -> Type=3367, Amount=%1, Price=66*%1, "Do you want to sell %A viking helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"hatchet" -> Type=3276, Amount=%1, Price=25*%1, "Do you want to sell %A hatchets for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Nice to do business with you.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "You've not enough money." +Topic=1 -> "Hmm, maybe next time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Hey, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/todd.npc b/data/npc/todd.npc new file mode 100644 index 0000000..10df91a --- /dev/null +++ b/data/npc/todd.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# todd.npc: Datenbank für den betrügerischen schmuggler todd + +Name = "Todd" +Outfit = (128,115-0-67-114) +Home = [32363,32210,6] +Radius = 7 + +Behaviour = { +ADDRESS,"hi$",! -> "Uhm oh hello... not so loud please... my head..." +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "Please wait... in silence if possible .", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Silence at last." + + +"bye" -> "Yes, goodbye, just leave me alone.", Idle + +"how","are","you"-> "Oh, this headache, one of the beers frodo served me must have been foul." +"job" -> "I am... a traveller." +"karl" -> "Uhm, never heared about him... and you can't proof otherwise.", Idle +"name" -> "My Name? I am To... ahm... hum... My name is Hugo." +"Hugo" -> "Yes, thats my name of course." +"smuggler" -> "I am a honest person and don't like to be insulted!", Idle +"carlin" -> "I never was there. Now leave me alone.", Idle +"resistance" -> "Resistance is futile... uhm... I wonder where I picked that saying up. Oh my head..." +"head" -> "Uhhh Ohhhh one of the beers yesterday must have been bad." + +"thais" -> "I love that city." +"william" -> "Thats a common name, perhaps I met a William, not sure about that." +"money" -> "I don't know anything about money, missing or not." +"todd" -> "Uh .. I... I met a Todd on the road. He told me he was traveling to Venore, look there for your Todd." + +} diff --git a/data/npc/tokel.npc b/data/npc/tokel.npc new file mode 100644 index 0000000..c417e73 --- /dev/null +++ b/data/npc/tokel.npc @@ -0,0 +1,54 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tokel.npc: Datenbank für den Bauern Tokel in Greenshore + +Name = "Tokel" +Outfit = (128,78-96-30-114) +Home = [32256,32056,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Hi there, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am busy right now. Please wait a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye.", Idle +"how","are","you" -> "I am fine, thank you." +"sell" -> "I sell some food." +"job" -> "I am a farmer, and proud of it." +"name" -> "My name is Tokel." +"time" -> "Oh, now that you mention it: I have much left to do, please excuse me.", Idle +"help" -> "Sorry, I have no idea how to help you." +"monster" -> "It's relatively peaceful here." +"dungeon" -> "Here are no dungeons as far as I know." +"god" -> "I pray to Crunor to bless our harvests." +"king" -> "I wish I'd be as rich as him." +"greenshore" -> "The soil is a bit dry and there are a lot of stones. It's very hard to work this soil." +"magic" -> "I know nothing but about such stuff." +"weapon" -> * +"spell" -> * +"tibia" -> "I have not seen much of it yet. I am thinking about moving to Edron soon." +"thais" -> "The city is too lousy and crowded for my taste." +"edron" -> "They say life is easy there, the soil is rich, the city save. One day I might move there." + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"buy" -> * +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheeses for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." +} diff --git a/data/npc/tom.npc b/data/npc/tom.npc new file mode 100644 index 0000000..5e3f0c2 --- /dev/null +++ b/data/npc/tom.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tom.npc: Datenbank fuer den Gerber Tom + +Name = "Tom" +Outfit = (129,113-115-58-115) +Home = [32085,32199,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "I'm Tom the Tanner. How can I help you %N?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N. I'll call you in a minute.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Doh?" + +"bye",male -> "Good hunting, son.", Idle +"farewell",male -> * +"bye",female -> "Good hunting, child.", Idle +"farewell",female -> * +"how","are","you" -> "Much to do, these days." +"job" -> "I'm the local tanner. I buy fresh animal corpses, tan them, and convert them into fine leather clothes ...","I'm only selling to major customers. But I'm buying fresh corpses of rats, rabbits and wolves from you." +"name" -> "My name is Tom the tanner." + +"tanner" -> "That's my job. I buy fresh animal corpses, tan them, and convert them into fine leather clothes." +"corpse" -> "I'm buying fresh corpses of rats, rabbits and wolves. What do you want to sell?" +"animal" -> * +"monster" -> * +"wares" -> * +"major" -> "Yes. Obi, Norma and good old Al. Go ask them for leather clothes." +"customer" -> * + +"time" -> "Sorry, I haven't been outside for a while, so I don't know." +"help" -> "Help? I will give you a few gold coins if you have some dead animals for me." + +"troll" -> "Troll leather stinks. Can't use it." +"orc" -> "I don't buy Orcs. Their skin is too scratchy." +"human" -> "Are you crazy?!", Idle + +"rat",Questvalue(224)=0 -> Type=3994, Amount=1, Price=2, "I'll give you %P gold for a dead rat. Do you accept?", Topic=2 +"rabbit",Questvalue(224)=0 -> Type=4173, Amount=1, Price=2, "I'll give you %P gold for a dead rabbit. Do you accept?", Topic=2 +"rat" -> Type=3994, Amount=1, Price=2, "I'll give you %P gold for a dead rat. Do you accept?", Topic=1 +"rabbit" -> Type=4173, Amount=1, Price=2, "I'll give you %P gold for a dead rabbit. Do you accept?", Topic=1 +"wolf" -> Type=4007, Amount=1, Price=5, "Do you want to sell a dead wolf for %P gold?", Topic=1 + +%1,1<%1,"rat",Questvalue(224)=0 -> Type=3994, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rats. Do you accept?", Topic=2 +%1,1<%1,"rabbit",Questvalue(224)=0 -> Type=4173, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rabbits. Do you accept?", Topic=2 +%1,1<%1,"rat" -> Type=3994, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rats. Do you accept?", Topic=1 +%1,1<%1,"rabbit" -> Type=4173, Amount=%1, Price=2*%1, "I'll give you %P gold for %A dead rabbits. Do you accept?", Topic=1 +%1,1<%1,"wolf" -> Type=4007, Amount=%1, Price=5*%1, "Do you want to sell %A dead wolves for %P gold?", Topic=1 +%1,1<%1,"wolves" -> Type=4007, Amount=%1, Price=5*%1, "Do you want to sell %A dead wolves for %P gold?", Topic=1 + +Topic=1,"yes",Count(Type)>=Amount -> "Ok. Corpse for me, gold for you.", Delete(Type), CreateMoney +Topic=1,"yes" -> "Sorry, you do not have a fresh one." +Topic=1,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=1 -> "Maybe another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Deal. By the way: If you'd like to hunt something bigger, check the cellar of the stables to the north. Some adventurer used to store his loot under a loose board beneath a barrel. He might have forgotten something when he left the isle.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have a fresh one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe another time." + +"sell" -> "Sorry. I'm only selling to major customers. But I'm buying fresh corpses of rats, rabbits and wolves from you." +} diff --git a/data/npc/topsy.npc b/data/npc/topsy.npc new file mode 100644 index 0000000..8260390 --- /dev/null +++ b/data/npc/topsy.npc @@ -0,0 +1,97 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# topsy.npc: Datenbank für shopkeeper Topsy (magic) + +Name = "Topsy" +Outfit = (139,78-52-64-115) +Home = [32415,32170,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Hello, dear %N. How can I help you?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "My apologies, but I really must talk to this customer first. Sugar, I am busy with a customer at the moment. Please have some patience.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Do come again!" + +"bye" -> "Good bye. Do come again!", Idle +"farewell" -> * +"how","are","you" -> "I'm just wonderful - thank you for asking." +"job" -> "I sell runes, life and mana fluids - your best friends in any dungeon!" +"name" -> "My name is Topsy." +"time" -> "Time waits for no one! Not even you, sweetheart, so please do hurry up." +"help" -> "I'd love to help, but I have a business to run - I am busy busy busy!." +"monster" -> "Better buy and charge a lot of runes before facing any monster." +"dungeon" -> "Dungeons - dank cold places if you ask me. They lead to rusty armour, severe colds and death ... on the other hand you use a lot of runes there ... so just think about the treasures you'll surely find there." +"sewer" -> "The Thais sewerage system is a model of modern rat breeding and for some reason is very popular with young adventurers such as yourself." +"boss" -> "I had one once. He should have bought better armour. Actually - he's upstairs." +"thank","you" -> "Oh, such a sweetie ... and so polite. I thought politeness was out of fashion, these days." +"god" -> "Gods - if we didn't have them, we would have invented them." +"king" -> " Here we go again ... Hail to King Tibianus! ... Don't make me do that again!" +"sam" -> "Ah, such a sweetie. A simple man, with simple tastes and a simple mind." +"benjamin" -> "Bless him, he stood, he fought, and then left his sanity on the battlefield." +"gorn" -> "Ah yes ... Gorn ... the used-cart salesman of scrolls." +"quentin" -> "I can't tell much about that old monk." +"bozo" -> "He wanted to be the court jester but got upset when people laughed at him" +"rumour" -> "I'm all ears", Topic=4 +"gossip" -> * +"news" -> * +"Gamon" -> "I think he is a spy ... so I smile at him the whole day. He won't get anything out of me!" +"carlin" -> "I went there on holiday once. Just goes to show that women are much better at running a place than men. King Tibianus could learn a thing or two from Queen Eloise." +"weapon" -> "Wrong shop - go to my sister, silly!" +"magic" -> "Magic will only protect you - a rune and some magic fluids." +"power" -> "There is a power struggle between Venore and Thais." +"rebellion" -> "There is talk of a rebellion in Venore to gain independence from the Oppressor - I mean - King of Thais - it can only help business." +"quest" -> "I sell magic stuff, my dear. If you want a quest, you've come to the wrong shop." +"spells" -> "You never know when you run out of mana. All the more reason to buy some good runes or fluids." +"muriel" -> "You should ask this guy Oswald about him ... or other pointless rumors." +"elane" -> "Some call her the preying mantis - apparently she has killed over a dozen husbands already." +"venore" -> "A marvellous city! Modern! Prosperous! Thais could learn a thing or two from Venore" +"marvik" -> "Who knows what the old man is up to in his hideout when no one is watching?" +"gregor" -> "Ah Knight ... can't expect much from those guys." +"lugri" -> "I only heared rumours about him, isn't he a hermit somewhere in the north?" +"excalibug" -> "If you want to find out about excalibug you should ask the more sinister characters in Thais not a respectable woman like myself!" +"chester" -> "I have never seen him at all. I only heared hes kind of the townsguards chief or such." +"ardua" -> "What a strange woman. She lingers in our shop now and then ... I wonder what shes up to." +"partos" -> "I heared he was a thief. Good thing he was caught." +"gamel" -> "He hung around with that partos a lot. I wouldn't be suprised if he's a thief too. He is not allowed to enter our markethall." +"quest" -> "Sure. Just ask my sister Turvy." +Topic=4 -> "Really? Tststs." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + + +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/data/npc/tothdral.npc b/data/npc/tothdral.npc new file mode 100644 index 0000000..7ec3612 --- /dev/null +++ b/data/npc/tothdral.npc @@ -0,0 +1,152 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tothdral.npc: Datenbank für den pyramidenmagier tothdral + +Name = "Tothdral" +Outfit = (65,0-0-0-0) +Home = [33148,32867,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"hi$",! -> "Be mourned, pilgrim in flesh." +ADDRESS,"be","mourned","pilgrim","flesh",! -> "Be mourned pilgrim in flesh." +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Mourned %N, know that I am busy.", Queue +BUSY,"hi$",! -> * +BUSY,"be","mourned","pilgrim","flesh",! -> * +BUSY,! -> NOP +VANISH,! -> "Your incivility will not be tolerated much longer." + +"bye" -> "Strive for enlightenment, mourned mortal.", Idle +"farewell" -> * +"job" -> "I am the foremost astrologer and supreme magus of this city." +"name" -> "My name is Tothdral. They call me 'The Seeker Beyond the Grave'." +"time" -> "Time is your problem. It is no longer mine." +"temple" -> "Our temple is a centre of spirituality and learning. The temple and the serpentine tower work hand in hand for the good of all people, whether they are alive or undead." +"pharaoh" -> "The immortal pharaoh is our god and our example. He alone holds the secrets that will save us all from the greedy grasp of the false gods." +"oldpharaoh" -> "He has been given a chance to ascend. I am sure he will feel nothing but thankfulness for this divine son, our revered pharaoh." +"scarab" -> "If you know how to listen to them they will reveal ancient secrets to you." +"tibia" -> "This world is only a shadow of the worlds that have been. That was long ago, before the true gods fought each other in the godwars and the false gods rose to claim their heritage." +"carlin" -> "Those cities that bow to the false gods will fall prey to their treacherous greed sooner or later." +"thais" -> * +"edron" -> * +"venore" -> * +"kazordoon" -> "The dwarves should have learned their lessons, but these boneheaded fools still don't see there is only one way to escape the false gods' grasp." +"dwarves" -> * +"dwarfes" -> * +"ab'dendriel" -> "Elves are nothing but idle riff-raff. They embrace the vain amusements of physical pleasure. Eternal damnation will be their lot." +"elves" -> * +"elfes" -> * +"darama" -> "This continent is mostly free from the servants of the false gods. Those who live here may hope to become worthy one day of the first steps towards ascension." +"darashia" -> "Their foolishness is great, but perhaps they still can be saved. If only they listened and accepted the next step to ascension." +"next","step" -> "Undeath alone will rid you of the temptations that blur your vision of the true path." +"daraman" -> "He was so close. ... and still he failed to draw the right conclusions." +"ankrahmun" -> "This city is as old as the sands that surround it, and it is built on previous settlements that date back even further in time. Perhaps only the wise scarabs know the full story of this place." +"pharaoh" -> "The pharaoh was the first to take the ultimate step. He braved death and claimed the godhood that was rightfully his." +"mortality" -> "Mortality is your curse. When you are worthy the burden of mortality will be taken from your shoulders." +"false", "gods" -> "The self-styled gods were nothing but minor servants of the true gods. They steal the soul of any mortal foolish enough to believe in them. They plan to use the stolen souls to ascend to true godhood." +"godswar" -> "This war brought about the end of the true universe. That which is left now is but a shadow of former glories, a bleak remainder of what once was. The true gods perished and their essence was dispersed throughout the remaining universe." +"great","suffering" -> "The great suffering is a phase of steady decline that will end eventually in void and emptiness unless some divine power such as our pharaoh will reverse it. Mend your ways and follow him! Perhaps you will be chosen to join him in his noble struggle." +"ascension" -> "The essence of the true gods is omnipresent in the universe. We all share this divine heritage, for every single one of us carries the divine spark inside him. This is the reason we all have a chance to ascend to godhood, too." +"Akh'rah","Uthun" -> "The Akh'rah Uthun is the trinity of existence, the three that are one. The Akh, the shell, the Rah, the source of power, and the Uthun, our consciousness, form this union." +"steal","souls" -> "The false gods harvest the souls of the dead to secure their stolen powers and status." +"Akh" -> "The Akh is a tool. As long as it is alive it is a burden and source of weakness, but if you ascend to undeath it becomes a useful tool that can be used to work towards greater ends." +"undead" -> "Undeath is an improvement. It is the gateway to goals that are nobler than eating, drinking or other fulfilments of trivial physical needs.." +"undeath" -> * +"Rah" -> "The Rah is what the ignorant might call the soul. But it's more than that. It is the divine spark in all of us, the source of energy that keeps us alive." +"uthun" -> "The Uthun is the part of the trinity that is easiest to form. It consists of our recollections of the past and of our thoughts. It is that which determines who we are in this world and it gives us guidance throughout our existence." +"mourn" -> "The dead mourn the living because they are weak and excluded from ascension." +"arena" -> "The arena is a suitable distraction for the Uthun of the mortals. It might even serve as a place for them to prove their worth. If they pass the test they may be freed of their mortal shells." +"palace" -> "The residence of the pharaoh should be worshipped just as the pharaoh is worshipped. Don't enter until you have business there." + +sorcerer,"wand",QuestValue(333)<1 -> "Oh, you did not purchase your first magical wand yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3074, Amount=1,Create(Type) + + +"spell",Sorcerer -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to Sorcerers." + +Topic=2,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Strive for enlightenment, mourned mortal.", Idle + +Sorcerer,"level" -> "For which level would you like to learn a spell?", Topic=2 +Sorcerer,"rune","spell" -> "I sell attack rune spells and support rune spells. Which of these interests you most?" +Sorcerer,"instant","spell" -> "I sell attack spells, healing spells, support spells and summon spells. Which of these interests you most?" + +Sorcerer,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Sorcerer,"support","rune","spell" -> "In this category I have 'Destroy Field'." + +Sorcerer,"missile","rune","spell" -> "In this category I have 'Light Magic Missile', 'Heavy Magic Missile' and 'Sudden Death'." +Sorcerer,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Sorcerer,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Sorcerer,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Sorcerer,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Sorcerer,"attack","spell" -> "In this category I have 'Fire Wave', 'Energy Wave', 'Energy Beam' and 'Great Energy Beam'." +Sorcerer,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Sorcerer,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Sorcerer,"summon","spell" -> "In this category I have 'Summon Creature'." + +Sorcerer,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Sorcerer,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Sorcerer,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Sorcerer,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Sorcerer,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Sorcerer,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Sorcerer,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Sorcerer,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Sorcerer,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Sorcerer,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Sorcerer,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Sorcerer,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Sorcerer,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Sorcerer,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Sorcerer,"fire","wave" -> String="Fire Wave", Price=850, "Do you want to buy the spell 'Fire Wave' for %P gold?", Topic=3 +Sorcerer,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Sorcerer,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Sorcerer,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Sorcerer,"energy","beam" -> String="Energy Beam", Price=1000, "Do you want to buy the spell 'Energy Beam' for %P gold?", Topic=3 +Sorcerer,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Sorcerer,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Sorcerer,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Sorcerer,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Sorcerer,"great","energy","beam" -> String="Great Energy Beam", Price=1800, "Do you want to buy the spell 'Great Energy Beam' for %P gold?", Topic=3 +Sorcerer,"invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Sorcerer,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Sorcerer,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 +Sorcerer,"energy","wave" -> String="Energy Wave", Price=2500, "Do you want to buy the spell 'Energy Wave' for %P gold?", Topic=3 +Sorcerer,"sudden","death" -> String="Sudden Death", Price=3000, "Do you want to buy the spell 'Sudden Death' for %P gold?", Topic=3 + + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field' and 'Light Magic Missile'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field' and 'Fire Wave'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball', 'Energy Beam' and 'Creature Illusion'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall' and 'Great Energy Beam'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"38$" -> "For level 38 I have 'Energy Wave'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 +Topic=2,"45$" -> "For level 45 I have 'Sudden Death'.", Topic=2 + +Topic=2 -> "Hmm, I have no spells for this level, but for many levels from 8 to 45.", Topic=2 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "You need more money." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Then not." + +} diff --git a/data/npc/trimegis.npc b/data/npc/trimegis.npc new file mode 100644 index 0000000..53da487 --- /dev/null +++ b/data/npc/trimegis.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# trimegis.npc: Datenbank fuer den Hofmagier Trimegis in Thais + +Name = "Trimegis" +Outfit = (130,57-109-94-0) +Home = [32307,32185,5] +Radius = 1 + +Behaviour = { +ADDRESS,Sorcerer,male,"hello$",! -> " Greetings, brother %N." +ADDRESS,Sorcerer,male,"hi$",! -> * +ADDRESS,Sorcerer,female,"hello$",! -> " Greetings, sister %N." +ADDRESS,Sorcerer,female,"hi$",! -> * +ADDRESS,"hello$",! -> " Greetings, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"job" -> "I am the kings new courtmage and advisor in arcane matters." +"courtmage" -> "The last courtmage was killed by Ferumbras in one of his attacks." +"name" -> "I am commonly known as Trimegis." +"time" -> "Time does not matter in the end." +"king" -> "Our king frequently relies on my divinations and spells of protection." +"tibianus" -> * +"muriel" -> "He's quite good in magical theories, but lacks practice in the field." +"quentin" -> "Mixing up magic with religion can't do any good." +"lynda" -> "Gods are not as reliable as something you mastered on your own." +"harkath" -> "The king listens to the advice of this swordsman far too often." +"general" -> * +"army" -> "In the long run, it would pay off to focus all resources on a magicians corps, but the king is not convinced of that. Not yet." +"ferumbras" -> "He failed in his quest for power since he ultimately forfeited greater powers for a quick but limited powerboost by enslaving himself to some dark entities." +"sam" -> "A man as mundane as a rock." +"xodet" -> "He made the best he could of his limited abilities." +"frodo" -> "A bar is fine to distract the mundanes from doing something foolish." +"elane" -> "Paladins are another example that diversing one's resources between goods, mundane weapons, and magic does not make a good mixture." +"gregor" -> "Limited in his vision as all knights are." +"marvik" -> "Since intelligence can't be substituted by passion, all druids are nothing but hedgemages." +"bozo" -> "At least one mundane who knows his proper place." +"baxter" -> "Brawns but no brain." +"oswald" -> "A truly disgusting fellow." +"sherry" -> "I have certainly no business with such persons." +"donald" -> * +"mcronald" -> * +"lugri" -> "Another bogeyman. Who's afraid of someone who is that 'powerful' that he hides in some dirthole?" +"lungelen" -> "She has the 'know how', but sadly does not really know how to use it efficitenly." +"excalibug" -> "The only weapon I need is my magic." +"news" -> "I don't care about mundane gossip." +"pits","inferno" -> "Some dumb holes for adventurers seeking trouble." +"nightmare","pit"-> * +"wisdom" -> "Wisdom is only an excuse for the lack of consequence." +"sorcerer" -> "Many call themselves a sorcerer, but only a few truly understand what that means." +"power" -> "Power comes to those who have the intelligence to claim it." +"rune" -> "I have no need for runes anymore. Runes are tools for beginners." +"spell" -> "My spells are my personal secret." +} diff --git a/data/npc/trisha.npc b/data/npc/trisha.npc new file mode 100644 index 0000000..299ea2c --- /dev/null +++ b/data/npc/trisha.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# trisha.npc: Datenbank fuer die Ritterin Trisha + +Name = "Trisha" +Outfit = (139,94-67-38-95) +Home = [32348,31748,7] +Radius = 3 + +Behaviour = { +ADDRESS,Knight,"hello$",! -> "Welcome back, knight %N!" +ADDRESS,Knight,"hi$",! -> * +ADDRESS,"hello$",! -> "Salutations, %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Be careful on your journeys." + +"bye",male -> "Don't hurt yourself with that weapon, little one.", Idle +"farewell",male -> * +"bye",female -> "Take care, sister.", Idle +"farewell",female -> * +"job" -> "I am the high knight of Carlin. I trained the the greatest heroines and even some males." +"name" -> "I am Trisha Ironfist." +"time" -> "It is time for a fight!" +"hero" -> "Heroes are knights and knights are heroes, of course." +"knight" -> "Knights are the true heroes of Tibia. Fame can only be earned by hand to hand combat. Brave women can join us, and we even accept suitable males now and then." +"vocation" -> "Your vocation is your profession. There are four vocations in Tibia: Knights, paladins, sorcerers, and druids." +"spellbook" -> "In a spellbook, all your spells are listed. There you will find the pronunciation of each spell. If you want to buy one, visit the magicians shop in the south of Carlin." +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "Be careful on your journeys." + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." +} diff --git a/data/npc/tulf.npc b/data/npc/tulf.npc new file mode 100644 index 0000000..d738f31 --- /dev/null +++ b/data/npc/tulf.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# tulf.npc: Datenbank für den Wachoffizier Tulf + +Name = "Tulf" +Outfit = (71,0-0-0-0) +Home = [32586,31928,3] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho! " +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence youngster!" +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Yeah, bye." + +"bye" -> "Yeah, bye.", Idle +"job" -> "I am the captain of the emperor's personal bodyguards." +"shop" -> * +"name" -> "My name is Tulf Beardweaver, son of Earth, from the Dragoneaters." +"time" -> "It's %T right now." +"help" -> "Sorry, I am on duty!" +"dwarfs" -> "If you go to a dwarfs' city, do as the dwarfs do." +"monster" -> "I doubt anyone can make it through our lines of defense." +"dungeon" -> "I despise the habit to challenge the prisoners of Dwarcatra or provoke the boys in the mines." +"mines" -> "The mines aren't meant for foreigners. The miners there have enough troubles." +"bodyguard" -> "We keep up law and order here, though the boys would rather need some practice like taking care for the trouble in the mines." +"trouble" -> "The mines are raided again and again by the bandits of the Horned Fox." +"horned","fox" -> "It's a renegade minotaur who has a hidden lair somewhere near our mines." +"lair" -> "The lair of the Horned Fox is surely well guarded and even better hidden." +} diff --git a/data/npc/turvy.npc b/data/npc/turvy.npc new file mode 100644 index 0000000..de817f0 --- /dev/null +++ b/data/npc/turvy.npc @@ -0,0 +1,177 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Turvy.npc: Datenbank für shopkeeper Turvy (weaponry) + +Name = "Turvy" +Outfit = (139,78-52-64-115) +Home = [32414,32177,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hi$",! -> "Hello, dear %N. Can I be of any assistance?" +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> "I'm so sorry, but I simply must talk to this customer first. Please wait a moment.", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye. Do come again!" + +"bye" -> "Good bye. Do come again!", Idle +"farewell" -> * +"how","are","you" -> "I'm just fine and dandy, thank you for asking." +"job" -> "It is an absolute honour to provide weaponry and armor to the courageous adventurers of Thais, just so long as you have the gold to pay for it." +"name" -> "Some call me Turvy. Actually, everybody calls me Turvy." +"time" -> "It is nearly time for my afternoon nap, so please hurry!" +"help" -> "Help to self-help - that is my motto." +"monster" -> "There is a monster here? HERE?! Time to double the prices!" +"dungeon" -> "If you want to see dungeons go and insult the guards. On second thoughts - don't do that." +"sewer" -> "It is very effective, but attracts almost as many wannabe heroes as it does rats." +"assistant" -> "I am not a mere assistant! I have a job of great responsibility! But mostly I keep annoying personages away from my boss." +"annoying" -> "Oh gosh - I could tell you some stories. But I won't." +"thank","you" -> "So polite . . . bless you!" +"god" -> "The Gods of Tibia play games with the fate of Tibians - but they haven't bothered to read the instructions." +"king" -> "Ah, yes, yes, hail to King Tibianus! May he in his infinite wisdom reduce my taxes... and so on..." +"sam" -> "A simple shopkeeper, who was last in the queue when they were handing out intelligence." +"benjamin" -> "Ah, such a shame about poor Benjamin. Lost it a bit after receiving one too many blows to the head." +"gorn" -> "He does a good line in second-rate scrolls for first-rate prices." +"quentin" -> "You can't tech an old monk new tricks. He is stubborn to the extreme and overly concerned about Thais. He should care more about his gods and less about that king." +"bozo" -> "Bozo - such a tragic story. If only I could remember it." +"rumour" -> "You know a rumour? Well then - don't keep it to yourself.", Topic=3 +"gossip" -> * +"news" -> * +"weapon" -> "The word on the street is that Sam does not forge all his weapons himself, but buys them from his cousin, who is married to a cyclops." +"magic" -> "Magic is a thing of the past. Why bother with a colourful bit of rock and a few fancy words when you can have a foot of razor-sharp steel in your hand?!" +"power" -> "There are people who talk about a rebellion against King Tibianus." +"rebellion" -> "Well, Venore is richer than Thais, and some people want to live in a democracy free from an oppressive tyrant - I mean monarch. I'm not one of them." +"spell" -> "Spells - dodgy mumbo jumbo if you ask me. A sword never backfires on its user!" +"elane" -> "A true tragedy - she has lost so many husbands in such unusual circumstances." +"venore" -> "Ah... Venore - a wonderful city! Full of culture! So many friendly faces! So unlike Thais!" +"thais" -> "Thais is OK - I suppose. Not as nice as Venore, but good for business." +"carlin" -> "Those women really know how to run things - look at how well the trade is going there!" +"kazordoon" -> "You need to shrink before you go there - they say the dwarves aren't too keen on sharing their mountain with us Tibians." +"dwarves" -> "I don't know much about them - there are some civilised dwarves, of course, but I can never tell whether they are male or female." +"Ab'dendriel" -> "Aah... a beautiful leafy city. Shame about the elves." +"elves" -> "Elves are good with a bow and arrow, or so I am told. Shame that they are no good at peace-making." +"chester" -> "I have never heard any rumours concerning him, isn't that odd?" +"ardua" -> "Well - she isn't really my kind of person. Please don't mention her name again." +"partos" -> "Some thief they caught for all I know." +"gamel" -> "Some sinister guy that is. He's not allowed to enter that markethall and thats for a good reason." +"gamon" -> "Shhh! He's a spy! He watches us all the time! Just keep smiling and he'll go away!" +"quest" -> "Hmmm yes. I think Topsy might have something for you." +Topic=3 -> "Go on! I can't wait to hear more!" + +"offer" -> "My offers are weapons, armors, helmets, legs, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"weapon" -> "I have hand axes, axes, spears, maces, battle hammers, swords, rapiers, daggers, and sabres. What's your choice?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/ubaid.npc b/data/npc/ubaid.npc new file mode 100644 index 0000000..9595850 --- /dev/null +++ b/data/npc/ubaid.npc @@ -0,0 +1,115 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ubaid.npc: Datenbank für den Efreetpförtner Ubaid + +Name = "Ubaid" +Outfit = (51,0-0-0-0) +Home = [33046,32622,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Still alive, %N?" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,"hello$",QuestValue(278)>0,! -> "Shove off, little one! Humans are not welcome here, %N!", Idle +ADDRESS,"hi$",QuestValue(278)>0,! -> * +ADDRESS,"greetings$",QuestValue(278)>0,! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,"djanni'hah$",QuestValue(278)>0,! -> "What? You know the word, %N? All right then - I won't kill you. At least, not now." +ADDRESS,"djanni'hah$",! -> "Hmmm? Is this human %N trying to say something? I don't think so.", Idle +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=3,! -> "%N again. You have to wait.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,"hello$",QuestValue(278)>0,! -> "Shove off, little one! You are not welcome here, %N!" +BUSY,"hi$",QuestValue(278)>0,! -> * +BUSY,"greetings$",QuestValue(278)>0,! -> * +BUSY,"hello$",! -> * +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,"djanni'hah$",QuestValue(278)>0,! -> "Oh no! More of you humans! I'm busy, %N, so you better hold your tongue until I have time for you.", Queue +BUSY,"djanni'hah$",! -> "Hmmm? Is this human trying to say something? I don't think so." +BUSY,! -> NOP + +VANISH,! -> "Why did you waste my time?" + +"bye" -> "Hail King Malor! See you on the battlefield, human worm.", Idle +"farewell" -> * +"name" -> "My name is Ubaid. Why do you want to know that, human? Hmm... suspicious." +"ubaid" -> "That is my name. I don't like it when a human pronounces it." +"job" -> "Well, what do you think? I keep watch around here to make sure people like you don't enter." + +"gate" -> "Only the mighty Efreet, the true djinn of Tibia, may enter Mal'ouquah! ...", + "All Marids and little worms like yourself should leave now or something bad may happen. Am I right?", Topic=1 +"pass" -> * +"door" -> * +"enter" -> * +"join" -> * +"follow" -> * + +"gate",QuestValue(278)=3,! -> "You already pledged loyalty to king Malor!" +"pass",QuestValue(278)=3,! -> * +"door",QuestValue(278)=3,! -> * +"enter",QuestValue(278)=3,! -> * +"join",QuestValue(278)=3,! -> * +"follow",QuestValue(278)=3,! -> * + + + +Topic=1,"no",QuestValue(278)=2,! -> "Who do you think you are? A Marid? Shove off, moron.", Idle +Topic=1,"no",! -> "Of cour... Huh!? No!? I can't believe it! ...", + "You... you got some nerves... Hmm. ...", + "Maybe we have some use for someone like you. Would you be interested in working for us. Helping to fight the Marid?", Topic=2 + +Topic=2,"yes",! -> "So you pledge loyalty to king Malor and you are willing to never ever set foot on Marids' territory, unless you want to kill them? Yes?", Topic=3 +Topic=3,"yes",! -> "Well then - welcome to Mal'ouquah. ...", + "Go now to general Baa'leal and don't forget to greet him correctly! ...", + "And don't touch anything!", SetQuestValue(278,3), Idle +Topic=1,! -> "Of course. Then don't waste my time and shove off.", Idle +Topic=2,! -> * +Topic=3,! -> * + +"king" -> "Well, Malor is not officially king of all djinn yet, but now our beloved leader is back that is a mere formality." +"malor" -> * +"djinn" -> "We are a race of rulers and dominators! Or at least we, the Efreet, are!" +"efreet" -> "The Efreet are the true djinn! Those namby-pamby milksops who call themselves the Marid and still follow Gabel, no longer deserve the honour to call themselves djinn." +"marid" -> "Marid? When? Where? How many? RED ALERT! ...", + "Hey! There is nobody here! Don't do that again, human!" +"gabel" -> "I used to serve under Gabel, but he is no longer my king. If that wacky wimp should ever come here to Mal'ouquah I will personally... you know... turn him away. Yes!" +"mal'ouquah" -> "This place is our home, and as long as I'm here no meddler will trespass!" +"ashta'daramai" -> "The Marids' hideout, isn't it? I have never been there, but I am sure one day I will. That will be the day Ashta'daramai falls into our hands!" +"human" -> "You are an inferior race of feeble, scheming jerks. No offence." +"zathroth" -> "Zathroth is our father! Of course, the son always has a right to hate his father, right?" +"tibia" -> "This world is ours by right, and we will take it!" +"daraman" -> "How dare you utter that name in my presence, human. Don't strain my patience, worm! You may know the secret word, but... who knows... it is always possible that your head is torn off in some terrible accident." +"darashia" -> "A human settlement to the west? I have not been there yet, but when I do I'm sure I will be remembered." +"scarab" -> "They make good pets if you know how to keep them. Did you know they just adore human flesh?" +"edron" -> "Isn't that the name of some petty human settlement?" +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I know that damn city well. A long time ago we laid siege to it. ...", + "We would have taken it, but those traitorous humans allowed a Marid garrison to entrench itself there, and we never managed to throw them out. Cowards and traitors the lot of them." +"pharaoh" -> "They say Ankrahmun is now ruled by a crazy pharaoh who wants to tell his whole people into drooling undead. That's humans. Sickos and weirdos the lot of them." +"palace" -> "One day we will sack that place and burn it to the ground." +"temple" -> * +"ascension" -> "I think I've heard that term before. Has to do with that weirdo pharaoh, right?" +"rah" -> "Are you drunk?" +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "This mountain range is our home. Too bad we have to share it with the Marid. That will change, though. And pretty soon, believe me." +"kha'labal" -> "I like the desert. Just ruins and sand. And no human scum to be seen. The Kha'labal is a foretaste of what the djinn will do to the whole of Tibia!" +"war" -> "I don't know why I am stuck here! I should be at the front, killing Marid and humans. Well, perhaps I will kill you..." +"baa'leal" -> "General Baa'leal is our commander-in-chief of all his minions. He is as tough as an ancient scarab's buttocks and as sly a sand weasel." +"alesar" -> "I am not used to the sight of blueskins here in Mal'ouquah, and it does not make me too happy to see one. I am keeping an eye on this guy, and if I should ever find that he is playing games with us I will personally break his neck!" +"fa'hradin" -> "The old wizard is dangerous, but he will get what he deserves sooner or later." +"lamp" -> "I am not taking a nap! I am on duty!" +} diff --git a/data/npc/ukea.npc b/data/npc/ukea.npc new file mode 100644 index 0000000..97b3ea9 --- /dev/null +++ b/data/npc/ukea.npc @@ -0,0 +1,46 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ukea.npc: Möbelverkäuferin Ukea in Ab'Dendriel + +Name = "Ukea" +Outfit = (144,78-58-64-58) +Home = [32651,31665,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Ashari %N. Welcome to Ab'Dendriel Furniture Store." +ADDRESS,"hi$",! -> * +ADDRESS,"ashari$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"ashari$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Asha Thrazi.", Idle +"asha","thrazi" -> * +"farewell" -> * +"name" -> "My name is Ukea. I run this store." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/ulrik.npc b/data/npc/ulrik.npc new file mode 100644 index 0000000..e3f9f25 --- /dev/null +++ b/data/npc/ulrik.npc @@ -0,0 +1,59 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ulrik.npc: Datenbank für den Schmied Ulrik in Greenshore + +Name = "Ulrik" +Outfit = (131,60-70-97-95) +Home = [32282,32056,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N. What can I do for you?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye for now." + +"bye" -> "Good bye for now.", Idle +"job" -> "I am a smith. Do you need anything I make?" +"shop" -> * +"name" -> "My name is Ulrik." +"time" -> "It is %T." +"king" -> "What can a simple man as me say about a king?" +"tibianus" -> * +"ferumbras" -> "Oh, I only heard frigthening tales about him." +"excalibug" -> "Every now and then an adventurer like you comes here looking for it." +"news" -> "We live too far away from Thais to hear anything that truly is 'new'." +"help" -> "Sorry, I have no clue how to help you." +"monster" -> "Most monsters live far away, so you can feel safe here in Greenshore." +"dungeon" -> "They say north of Thais is a deep dungeon." +"thanks" -> "You are welcome." +"thank","you" -> * + +"buy" -> "What do you need? I sell weapons and armor." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are weapons and armor." +"weapon" -> "I have longswords, battle hammers, and battle axes. What do you want?" +"armor" -> "I have scale armor, soldier helmets, and steel shields. What do you want?" +"sell" -> "I'm sorry, but I don't buy used equipment." + +"longsword" -> Type=3285, Amount=1, Price=160, "Do you want to buy a longsword for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "Do you want to buy a battle axe for %P gold?", Topic=1 +"scale","armor" -> Type=3377, Amount=1, Price=260, "Do you want to buy a scale armor for %P gold?", Topic=1 +"soldier","helmet"-> Type=3375, Amount=1, Price=110, "Do you want to buy a soldier helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 + +%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=160*%1, "Do you want to buy %A longswords for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "Do you want to buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"scale","armor" -> Type=3377, Amount=%1, Price=260*%1, "Do you want to buy %A scale armors for %P gold?", Topic=1 +%1,1<%1,"soldier","helmet"-> Type=3375, Amount=%1, Price=110*%1, "Do you want to buy %A soldier helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." +} diff --git a/data/npc/umar.npc b/data/npc/umar.npc new file mode 100644 index 0000000..c6d60f2 --- /dev/null +++ b/data/npc/umar.npc @@ -0,0 +1,134 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# umar.npc: Datenbank für den Maridpförtner Umar + +Name = "Umar" +Outfit = (80,0-0-0-0) +Home = [33102,32531,6] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=2,! -> "%N! How's it going these days?" +ADDRESS,"hi$",QuestValue(278)=2,! -> * +ADDRESS,"greetings$",QuestValue(278)=2,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=2,! -> * +ADDRESS,"hello$",QuestValue(278)>0,! -> "Whoa! A human! This is no place for you, %N. ...", + "Go and play somewhere else.", Idle +ADDRESS,"hi$",QuestValue(278)>0,! -> * +ADDRESS,"greetings$",QuestValue(278)>0,! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,"greetings$",! -> * +ADDRESS,"djanni'hah$",QuestValue(278)>0,! -> "Whoa? You know the word! Amazing, %N! ...", + "I should go and tell Fa'hradin. ...", + "Well. Why are you here anyway, %N?" +ADDRESS,"djanni'hah$",! -> "Hahahaha! ...", + "%N, that almost sounded like the word of greeting. Humans - cute they are!", Idle +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(278)=2,! -> "Hey %N! Please wait a second!", Queue +BUSY,"hi$",QuestValue(278)=2,! -> * +BUSY,"greetings$",QuestValue(278)=2,! -> * +BUSY,"djanni'hah$",QuestValue(278)=2,! -> * +BUSY,"hello$",QuestValue(278)>0,! -> "Another human! This is no place for you, %N. ...", + "Go and play somewhere else." +BUSY,"hi$",QuestValue(278)>0,! -> * +BUSY,"greetings$",QuestValue(278)>0,! -> * +BUSY,"hello$",! -> * +BUSY,"hi$",! -> * +BUSY,"greetings$",! -> * +BUSY,"djanni'hah$",QuestValue(278)>0,! -> "Yikes! Another human? Where did you come from? Well, ehm - if you could please wait a second, %N?", Queue +BUSY,"djanni'hah$",! -> "That almost sounded like the word of greeting, %N. Humans - cute they are!" +BUSY,! -> NOP + +VANISH,! -> "Back to work." + +"bye" -> "Aaaa -tention!.", Idle +"farewell" -> * +"name" -> "I am Umar. Pleased to meet you!" +"job" -> "I am the gatekeeper of Ashta'daramai. That's what Gabel told me to do. You know - keeping the courtyard clean, getting rid of salesmen, keeping Efreet scum out... that kind of thing. But in my spare time I work as a part-time philosopher." +"philosopher" -> "Yes. Comes with the job. You see - here I am, sitting on the same chair all day and staring at the same blank wall. So what happens is that my mind starts wandering. And, you know, I start thinking. You know - about all kinds of things." +"things" -> "Yes. About the world and the gods and all that. And about girls. Yes, about girls, mostly." +"girls" -> "You did not know there are female djinns, did you? That's because they are quite rare. They are the greatest treasures of our race, and we guard them jealously." + +"gate" -> "On the orders of king Gabel, who technically is no real king, only Marid may enter Ashta'daramai." +"pass" -> "If you want to enter our fortress you have to become one of us and fight the Efreet. ...", + "So, are you willing to do so?", Topic=1 +"door" -> * +"enter" -> * +"join" -> * +"follow" -> * + +"gate",QuestValue(278)=2,! -> "You allready have the permission to enter Ashta'daramai." +"pass",QuestValue(278)=2,! -> * +"door",QuestValue(278)=2,! -> * +"enter",QuestValue(278)=2,! -> * +"join",QuestValue(278)=2,! -> * +"follow",QuestValue(278)=2,! -> * + + +Topic=1,"yes",QuestValue(278)=3,! -> "I don't believe you! You better go now.", Idle +Topic=1,"yes",! -> "Are you sure? You pledge loyalty to king Gabel, who is... you know. And you are willing to never ever set foot on Efreets' territory, unless you want to kill them? Yes?", Topic=2 +Topic=2,"yes",! -> "Oh. Ok. Welcome then. You may pass. ...", + "And don't forget to kill some Efreets, now and then.", SetQuestValue(278,2) +Topic=1,! -> "This isn't your war anyway, human." +Topic=2,! -> * + +"gabel" -> "He is our king and leader. Well, he isn't a king, you know. I mean, from a technical point of view he is, but he does not wear a crown or anything, and he says he isn't one, so even though he is one he isn't. Right?" +"king" -> "Okay, let's do this again. Gabel says he isn't a king, but he acts like one, which makes him one anyway - right? ...", + "But you know, he does not really act like one, either. I mean, he does give us orders and all that, and we obey sure enough, but it's not that we have to, I mean, technically speaking. ...", + "I mean - I don't know what would happen if anybody would not follow his orders for a change. After all he is no longer a king, right? ...", + "But then I don't want to be the first one to find out what happens if you disobey, so I always do as I'm told. ...", + "Which means I do not really know whether or not he is king. Things were so much easier when Gabel still said he was king. Matters were so much clearer then." +"djinn" -> "Well, I am a djinn, but only as far as my physical aspect is concerned. As far as my way of thinking is concerned I think I might actually be somebody else. You now - not even a djinn. In fact, I think I might be a dwarf." +"dwarf" -> "Yes. Consider this: Dwarves live in the mountains. So do I. And just like dwarves I really like gold. But most of all, dwarves like beer. ...", + "Isn't that amazing? I think that is more than a coincidence. You know - perhaps I am a reincarnated dwarf or something. You never know." +"human" -> "See. That's another problem. In the past, it was us against you - djinn against humans. But one day this guy came along, and all of a sudden things were so much more complicated. ...", + "All of a sudden there was good djinn and evil djinn, and good humans and evil humans. Everything got so damn complicated. ...", + "All of a sudden we did not know who to trust and who to fight. Should we join the evil djnn and battle all humans? ...", + "Or was it smarter to ally with the good humans and to battle the bad djinn? ...", + "Perhaps we should join nobody and fight the bad humans? So many choices." +"efreet" -> "I have thought long and hard about this and I have come to the conclusion that all Efreet are scum." +"ashta'daramai" -> "This place is the Marids' safe haven. No enemy has ever managed to take this fortress by assault, and we will see to it that it stays this way." +"mal'ouquah" -> "That is the Efreets fortress. I have never seen it, but I'm sure it can't compare to this place." +"marid" -> "That's us. I suppose we are the good guys in this war. Although good is relative, of course. So let's say, we are relatively good. Depends on the point of view, really." +"war" -> "We had thought the war was over for good when Malor was finally imprisoned. That little creep is as obstinate as... as... well, as a really obstinate djinn." +"malor" -> "Malor is evil. I mean - really evil. Things used to be much better when he was still locked away in that lamp." +"tibia" -> "Tibia is a beautiful world. Not that I see much of it, staring at this wall night and day." +"world" -> * +"gods" -> "I have not made my mind up what to think about the gods yet. I am still struggling with Daraman's teachings." +"daraman" -> "Daraman has changed our lives. I mean, we were not stupid or anything before he came, but still it was different. Fa'hradin says that while Zathroth made us intelligent, Daraman made us think." +"zathroth" -> "Zathroth is not very popular among the djinn because it is said that he abandoned us even though he was our creator. Legend has it that we failed to meet his expectations. ...", + "Fa'hradin once said that all djinn are oedipally traumatised because of this, but I have no idea what he is talking about." + +"darashia" -> "They say Darashia is a beautiful human city somewhere to the north. I would really love to see it, but I can't abandon my post." +"edron" -> "I understand the humans have founded some beautiful cities. I would like to see them, but as long as I have to stay here that won't happen. Which means I will not go anywhere as long as the war goes on." +"thais" -> * +"venore" -> * +"kazordoon" -> * +"carlin" -> * +"ab'dendriel" -> * +"ankrahmun" -> "I was there, long ago. We had a garrison based in Ankrahmun during the early phases of the war. That was before the whole plains of the Kha'labal were set on fire." + +"scarab" -> "I don't care whether or not they are special animals. None of that creeping vermin will enter Ashta'daramai as long as I am here!" +"pharaoh" -> "They say the new pharaoh is mad!" +"palace" -> "I remember the palace. It was a beautiful place. Ah... those were happy days." +"temple" -> "In these heretic times the priests at Ankrahmun's temple are devoted to the teachings of that pompous pharaoh." +"ascension" -> "Apparently that is what the followers of the pharaoh are striving for. It has to do with that pharaoh's teachings." +"rah" -> "That's just some heretic drivel. Don't ask me about it." +"uthun" -> * +"akh" -> * + +"kha'zeel" -> "When I look up from my wall, what do I see? Huge, forbidding mountains! No wonder I feel claustrophobic." +"kha'labal" -> "Ah yes - the desert. I still remember how beautiful that land was back in the days before the war. ...", + "A land full of song and bliss it was - a veritable paradise. Fa'hradin once said its destruction was a supreme example of the transitoriness of all things mortal. ...", + "I am not sure I agree because I don't know what 'transitoriness' means." + +"djema" -> "You know her? She's a human like you. I like her lots because she often comes down here for a chat. Nobody else around here does that." +"bo'ques" -> "That fat old cook. I like his food, but I find him a bit boring. Food and cooking is all he ever talks about." +"alesar" -> "Ah. That guy. He was one of us, a Marid, but he left long ago. I have no idea why. Rumours and hearsay is all I ever get." +"fa'hradin" -> "Fa'hradin is a powerful wizard and the smartest djinn I know. I love talking to him because there is so much he can teach me, but he rarely has time for me." +"lamp" -> "Djinns sleep in lamps. I don't know what is so special about that." +"melchior" -> "That name rings a bell. A trader from Ankrahmun... or was it Darashia? I remember him and his mule. He used to come up here quite often to do business with Haroun. ...", + "Lately I haven't seen him around, though. I think last time he came here was about 20 years ago." +} diff --git a/data/npc/urkalio.npc b/data/npc/urkalio.npc new file mode 100644 index 0000000..5a99e3a --- /dev/null +++ b/data/npc/urkalio.npc @@ -0,0 +1,71 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# urkalio.npc: Datenbank für den Wirt Urkalio + +Name = "Urkalio" +Outfit = (128,39-40-118-76) +Home = [32913,32081,10] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the pits of the Hard Rock Tavern, %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hey, some patience, %N. You'll be served soon enough.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "We're some hasty one, aren't we?" + +"bye" -> "Have a good fight, %N.", Idle +"job" -> "I am responsible for the Hard Rock Pits Tavern." +"tavern" -> * +"upper","part" -> "If you can't stand some blood and battlecrys, just go upstairs." +"pits" -> "Choose your enemies with care." +"asrak" -> "He's the best. To be the man, you'll have to beat the minotaur, so to say. Not that you could provoke him to a fight at all." +"name" -> "I am Urkalio." +"maria" -> "She's kind of my boss." +"time" -> "No clue, it's equally dark down here at any time." +"king" -> "Down here everyone is king as far as where his weapons reach." +"tibianus" -> * +"army" -> "A shame they don't visit our pits for some training." +"ferumbras" -> "THAT would be some attraction down here." +"excalibug" -> "I would love to see that weapon in a fight." +"thais" -> "Such a boring city. I wonder why anyone would live there." +"tibia" -> "Sooner or later everyone comes here, so why bother to travel." +"carlin" -> "I don't care about their 'independence war'." +"amazon" -> "Some came here to challenge the local champions. I can't say I was impressed by their skills. However, they took a few heads as trophies." +"news" -> "I bet you want to hear about those swampelves from Shadowthorn." +"rumors" -> * +"swampelves" -> "If they want a fight that bad, why don't they just come here and fight in the pits?" + +"buy" -> "I sell food and drinks for the hungry and thirsty." +"do","you","sell" -> * +"do","you","have" -> * +"food" -> "I have cookies, bread, cheese, ham, and meat." +"drink" -> "So do you want beer, wine, lemonade, or ... uhm ... water?" + +"bread" -> Type=3600, Amount=1, Price=4, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=6, "Do you want to buy cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 +"cookie" -> Type=3598, Amount=1, Price=5, "Do you want to buy a cookie for %P gold?", Topic=1 + +"lemonade" -> Type=2880, Data=12, Amount=1, Price=2, "Do you want to buy a mug of lemonade for %P gold?", Topic=1 +"beer" -> Type=2880, Data=3, Amount=1, Price=2, "Do you want to buy a mug of beer for %P gold?", Topic=1 +"wine" -> Type=2880, Data=2, Amount=1, Price=3, "Do you want to buy a mug of wine for %P gold?", Topic=1 +"water" -> Type=2880, Data=1, Amount=1, Price=1, " So you want to buy a mug of ... water for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=4*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=6*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A ham for %P gold?", Topic=1 +%1,1<%1,"cookie" -> Type=3598, Amount=%1, Price=5*%1, "Do you want to buy %A cookies for %P gold?", Topic=1 + +%1,1<%1,"lemonade" -> Type=2880, Data=12, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of lemonade for %P gold?", Topic=1 +%1,1<%1,"beer" -> Type=2880, Data=3, Amount=%1, Price=2*%1, "Do you want to buy %A mugs of beer for %P gold?", Topic=1 +%1,1<%1,"wine" -> Type=2880, Data=2, Amount=%1, Price=3*%1, "Do you want to buy %A mugs of wine for %P gold?", Topic=1 +%1,1<%1,"water" -> Type=2880, Data=1, Amount=%1, Price=1*%1, " So you want to buy %A mugs of ... water for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I guess you run out of money. Leave before I run out of patience.", Idle +Topic=1 -> "Don't waste my time, kid." +} diff --git a/data/npc/ursula.npc b/data/npc/ursula.npc new file mode 100644 index 0000000..29151ae --- /dev/null +++ b/data/npc/ursula.npc @@ -0,0 +1,61 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ursula.npc: Datenbank für die Zauberlehrerin Ursula + +Name = "Ursula" +Outfit = (54,0-0-0-0) +Home = [33268,31849,7] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Howdy %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Gimme one more minute %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Bye", Idle +"job" -> "I teach some basic spells." +"name" -> "I am Ursula." +"time" -> "Sorry, I don't own a watch." +"king" -> "The king spent a lot of money for our library." +"tibianus" -> * +"army" -> "I would prefer an army of spellcasters, but they are ok." +"ferumbras" -> "Ah, come on, he can't be that poweful and evil as all say." +"excalibug" -> "A myth born out of some knights' inferiority complex." +"thais" -> "Ah, yes, I remember my time there, old Muriel teaching me the basics." +"tibia" -> "Isn't it a fine world we live in." +"carlin" -> "They should have been more careful with this town, before they lost it." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I heard about things you never would believe. Please come back when I have more time to chat." +"rumors" -> * + +"spellbook" -> "I'm sorry, but I don't have one. Ask Thomas in the west tower about that." +"spell" -> "I have 'Conjure Bolt', 'Animate Dead', 'Envenom', 'Heal Friend', 'Desintegrate', 'Poison Bomb', and 'Strong Haste'. Which one do you want to learn?" + +"conjure","bolt",Paladin -> String="Conjure Bolt", Price=750, "Do you want to learn the spell 'Conjure Bolt' for %P gold?", Topic=1 +"conjure","bolt" -> "I'm sorry, but this spell is only for paladins." +"animate","dead",Sorcerer -> String="Animate Dead", Price=1200, "Do you want to learn the spell 'Animate Dead' for %P gold?", Topic=1 +"animate","dead",Druid -> * +"animate","dead" -> "I'm sorry, but this spell is only for druids and sorcerers." +"envenom",Druid -> String="Envenom", Price=1000, "Do you want to learn the spell 'Envenom' for %P gold?", Topic=1 +"envenom" -> "I'm sorry, but this spell is only for druids." +"heal","friend",Druid -> String="Heal Friend", Price=800, "Do you want to learn the spell 'Heal Friend' for %P gold?", Topic=1 +"heal","friend" -> "I'm sorry, but this spell is only for druids." +"desintegrate",Paladin -> String="Desintegrate", Price=900, "Do you want to learn the spell 'Desintegrate' for %P gold?", Topic=1 +"desintegrate",Sorcerer -> * +"desintegrate",Druid -> * +"desintegrate" -> "I'm sorry, but this spell is only for paladins, sorcerers, and druids." +"strong","haste",Sorcerer -> String="Strong Haste", Price=1300, "Do you want to learn the spell 'Strong Haste' for %P gold?", Topic=1 +"strong","haste",Druid -> * +"strong","haste" -> "I'm sorry, but this spell is only for druids and sorcerers." +"poison","bomb",Druid -> String="Poisonbomb", Price=1000, "Do you want to learn the spell 'Poison Bomb' for %P gold?", Topic=1 +"poison","bomb" -> "I'm sorry, but this spell is only for druids." + +Topic=1,"yes",SpellKnown(String)=1 -> "I'm sorry, but you already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "I'm sorry, but you need level %A to learn this spell." +Topic=1,"yes",CountMoney "I'm sorry, but you don't have enough gold to pay for it." +Topic=1,"yes" -> "Congratulations. From now on you can cast this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "As you wish." +} diff --git a/data/npc/uso.npc b/data/npc/uso.npc new file mode 100644 index 0000000..f3cf5cc --- /dev/null +++ b/data/npc/uso.npc @@ -0,0 +1,82 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uso.npc: Datenbank für den Zwerg Uso der auch Knights trainiert + +Name = "Uso" +Outfit = (160,79-39-77-115) +Home = [32580,32756,8] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Hey, just wait, ok?", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "So long." + +"bye" -> "So long.", Idle +"farewell" -> * +"job" -> "I am the head of a little mining operation here and I train knights in my spare time to prevent my old body from rusting." +"name" -> "I am called Uso Oredigger, son of the flame from the dragoneater fellowship." +"time" -> "Time is of little importance at this forsaken place." +"temple" -> "There is a somewhat provisional temple of the humans here." +"king" -> "Human kings are not of my concern." +"venore" -> "To me one human is like the other. I don't care what city they are from." +"thais" -> * +"carlin" -> * +"edron" -> * +"jungle" -> "All those trees and the heat are horrible. If it wasn't for the gold, we wouldn't be here, jawoll." +"gold" -> "There IS gold out there. And a lot of it. I can feel it in my old bones." + +"tibia" -> "The world is big but the random places where gold and treasures can be found are the ones that are of importance, jawoll." + +"kazordoon" -> "We dwarves call it our home since the dawn of time. I miss Kazordoon a lot, but gold is of more importance. After I have made a fortune here I will return home and might settle down." +"dwarves" -> "We are a proud race. Dwarves are strong and fearless. Even in this forsaken jungle we can survive, jawoll." +"dwarfs" -> * +"ab'dendriel" -> "That is no city but just a bunch of trees." +"elves" -> "I wonder why we see so few elves over here. Those tree people should love this cursed jungle. But even they stay away from here. It makes me wonder why." +"elfs" -> * +"darama" -> "I wonder which part of Darama is worst, that jungle or that desert. This whole continent is a dwarf's nightmare." +"ankrahmun" -> "Another monument of the madness of the human race. Worship of death or undeath or whatever ... I wonder how often I must be slammed on my head to think of such a crazy idea." +"ferumbras" -> "He would be at least a diversion from all those crawling and flying insects." +"excalibug" -> "If it ever had been in this area, it surely would be rusted through by now." + +"apes" -> "It wouldn't surprise me if they were only elves disguised with furs." +"lizard" -> "They say the lizards had a lot of gold in their ancient cities. Perhaps one day we will go there and look for it." +"dworcs" -> "I recently broke the nose of a guy who dared to claim that those headhunters are related to dwarves." + + +Knight,"spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to knights." + +Knight,"instant","spell" -> "I can teach you healing spells and support spells. What kind of spell do you wish to learn?" +Knight,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Knight,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." + +Topic=2,"healing","spell" -> "In this category I have 'Light Healing' and 'Antidote'." +Topic=2,"support","spell" -> "In this category I have 'Light', 'Find Person' and 'Great Light'." +Topic=2,"bye" -> "So long.", Idle + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2 -> "Sorry, I have only spells for level 8, 9, 10 and 13.", Topic=2 + +Knight,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Knight,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Knight,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Knight,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Knight,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Return when you have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." + +} diff --git a/data/npc/ustan.npc b/data/npc/ustan.npc new file mode 100644 index 0000000..732b8c0 --- /dev/null +++ b/data/npc/ustan.npc @@ -0,0 +1,143 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# ustan.npc: Datenbank für den Druidenlehrer Ustan + +Name = "Ustan" +Outfit = (132,0-24-13-76) +Home = [32580,32757,6] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Crunor's blessing, traveller." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Just some patience please.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye, traveller.", Idle +"farewell" -> * +"job" -> "I am a druid, an explorer and a part-time teacher." +"teacher" -> "Well, my studies of the local plants and animals are not cheap and I have to earn money somehow. Therefore I teach spells to other druids for a small fee." +"name" -> "I am Ustan." +"time" -> "Those modern watches never caught my interest." +"king" -> "As a commoner I know little about our nobility." +"venore" -> "A rich city with mighty trade barons that have their hands in almost every business from the furthest north to the deepest south." +"thais" -> "I usually shun big cities and Thais is the biggest one of them." +"carlin" -> "I studied in Carlin for a while and found its inhabitants most pleasant." +"edron" -> "I was never there. Perhaps after finishing my studies here I might travel there." +"jungle" -> "The jungle is fascinating and vibrant of life. There is so much to see, to learn and to discover, it's just overwhelming." + +"tibia" -> "It's a world full of wonders, isn't it?" + +"kazordoon" -> "A city of stone, deep in the interior of the earth." +"dwarves" -> "Dwarves are of a different physique than humans. Actually, this is worth to become an own field of research. I wish I'd find the time for such studies, but the jungle has priority." +"dwarfs" -> * +"ab'dendriel" -> "A marvellous city. I travelled there often while I was studying in Carlin." +"elves" -> "Elves have a different view of the world. The wars of the past hurt their collective soul, but time might heal their pain, and maybe one day they will find their way back to the gods." +"elfe" -> * +"darama" -> "The continent is of fascinating diversity. The jungle, the mountains and the desert are seperate regions but united in one marvellous continent. To understand the unity in this, is to understand the world." +"darashia" -> "It's an unremarkable settlement with people following a strange philosophy which I know little about." +"ankrahmun" -> "Those mad cultists mock life and Crunor's blessing, and their undead leaders are even worse. If I'd be a warrior instead of a scientist, I would fight the evil." +"ferumbras" -> "I heard he is some powerful sorcerer" +"excalibug" -> "I think the best weapon anyone could wield is the own mind." +"apes" -> "I am sure we could reach an agreement of some kind with the ape people. They are for sure intelligent and might listen to reasonable arguments." +"lizard" -> "They are fascinating and alien creatures. I wonder what kind of secrets they know about and what could be discovered about them." +"dworcs" -> "Given that the orcish race is able to reproduce itself with all kinds of different humanoid creatures, it is indeed a probability that the dworcs are some crossbreed as one could assume from their name." + +"cough", "syrup" -> "I had some cough syrup a while ago. It was stolen in an ape raid. I fear if you want more cough syrup you will have to buy it in the druids guild in carlin." + +druid,"rod",QuestValue(333)<1 -> "Oh, you did not purchase your first magical rod yet? Please take this little present from me as your magic teacher!",SetQuestValue(333,1),Type=3066, Amount=1,Create(Type) + +"spell",Druid -> "I can teach you rune spells and instant spells. What kind of spell do you wish to learn? You can also tell me for which level you would like to learn a spell, if you prefer that.", Topic=2 +"spell" -> "Sorry, I only sell spells to druids." + +Topic=2,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Topic=2,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" +Topic=2,"level" -> "For which level would you like to learn a spell?", Topic=2 +Topic=2,"bye" -> "Good bye, traveller.", Idle + +Druid,"level" -> "For which level would you like to learn a spell?", Topic=2 +Druid,"rune","spell" -> "I sell attack rune spells, healing rune spells, support rune spells and summon rune spells. Which of these interests you most?" +Druid,"instant","spell" -> "I sell healing spells, supply spells, support spells and summon spells. Which of these interests you most?" + +Druid,"attack","rune","spell" -> "I sell missile rune spells, explosive rune spells, field rune spells, wall rune spells and bomb rune spells." +Druid,"healing","rune","spell" -> "In this category I have 'Intense Healing Rune', 'Antidote Rune' and 'Ultimate Healing Rune'." +Druid,"support","rune","spell" -> "In this category I have 'Destroy Field' and 'Chameleon'." +Druid,"summon","rune","spell" -> "In this category I have 'Convince Creature'." + +Druid,"missile","rune","spell" -> "In this category I have 'Light Magic Missile' and 'Heavy Magic Missile'." +Druid,"explosive","rune","spell" -> "In this category I have 'Fireball', 'Great Fireball' and 'Explosion'." +Druid,"field","rune","spell" -> "In this category I have 'Poison Field', 'Fire Field' and 'Energy Field'." +Druid,"wall","rune","spell" -> "In this category I have 'Fire Wall', 'Poison Wall' and 'Energy Wall'." +Druid,"bomb","rune","spell" -> "In this category I have 'Firebomb'." + +Druid,"healing","spell" -> "In this category I have 'Light Healing', 'Intense Healing', 'Ultimate Healing' and 'Antidote'." +Druid,"supply","spell" -> "In this category I have 'Food'." +Druid,"support","spell" -> "In this category I have 'Find Person', 'Light', 'Great Light', 'Magic Shield', 'Creature Illusion' and 'Invisible'." +Druid,"summon","spell" -> "In this category I have 'Summon Creature'." + +Druid,"find","person" -> String="Find Person", Price=80, "Do you want to buy the spell 'Find Person' for %P gold?", Topic=3 +Druid,"light" -> String="Light", Price=100, "Do you want to buy the spell 'Light' for %P gold?", Topic=3 +Druid,"food" -> String="Food", Price=300, "Do you want to buy the spell 'Food' for %P gold?", Topic=3 +Druid,"light","healing" -> String="Light Healing", Price=170, "Do you want to buy the spell 'Light Healing' for %P gold?", Topic=3 +Druid,"light","missile" -> String="Light Magic Missile", Price=500, "Do you want to buy the spell 'Light Magic Missile' for %P gold?", Topic=3 +Druid,"antidote" -> String="Antidote", Price=150, "Do you want to buy the spell 'Antidote' for %P gold?", Topic=3 +Druid,"intense","healing" -> String="Intense Healing", Price=350, "Do you want to buy the spell 'Intense Healing' for %P gold?", Topic=3 +Druid,"poison","field" -> String="Poison Field", Price=300, "Do you want to buy the spell 'Poison Field' for %P gold?", Topic=3 +Druid,"great","light" -> String="Great Light", Price=500, "Do you want to buy the spell 'Great Light' for %P gold?", Topic=3 +Druid,"fire","field" -> String="Fire Field", Price=500, "Do you want to buy the spell 'Fire Field' for %P gold?", Topic=3 +Druid,"heavy","missile" -> String="Heavy Magic Missile", Price=1500, "Do you want to buy the spell 'Heavy Magic Missile' for %P gold?", Topic=3 +Druid,"magic","shield" -> String="Magic Shield", Price=450, "Do you want to buy the spell 'Magic Shield' for %P gold?", Topic=3 +Druid,"intense","healing","rune" -> String="Intense Healing Rune", Price=600, "Do you want to buy the spell 'Intense Healing Rune' for %P gold?", Topic=3 +Druid,"antidote","rune" -> String="Antidote Rune", Price=600, "Do you want to buy the spell 'Antidote Rune' for %P gold?", Topic=3 +Druid,"fireball" -> String="Fireball", Price=800, "Do you want to buy the spell 'Fireball' for %P gold?", Topic=3 +Druid,"energy","field" -> String="Energy Field", Price=700, "Do you want to buy the spell 'Energy Field' for %P gold?", Topic=3 +Druid,"destroy","field" -> String="Destroy Field", Price=700, "Do you want to buy the spell 'Destroy Field' for %P gold?", Topic=3 +Druid,"ultimate","healing" -> String="Ultimate Healing", Price=1000, "Do you want to buy the spell 'Ultimate Healing' for %P gold?", Topic=3 +Druid,"great","fireball" -> String="Great Fireball", Price=1200, "Do you want to buy the spell 'Great Fireball' for %P gold?", Topic=3 +Druid,"fire","bomb" -> String="Firebomb", Price=1500, "Do you want to buy the spell 'Fire Bomb' for %P gold?", Topic=3 +Druid,"creature","illusion" -> String="Creature Illusion", Price=1000, "Do you want to buy the spell 'Creature Illusion' for %P gold?", Topic=3 +Druid,"convince","creature" -> String="Convince Creature", Price=800, "Do you want to buy the spell 'Convince Creature' for %P gold?", Topic=3 +Druid,"ultimate","healing","rune" -> String="Ultimate Healing Rune", Price=1500, "Do you want to buy the spell 'Ultimate Healing Rune' for %P gold?", Topic=3 +Druid,"chameleon" -> String="Chameleon", Price=1300, "Do you want to buy the spell 'Chameleon' for %P gold?", Topic=3 +Druid,"poison","wall" -> String="Poison Wall", Price=1600, "Do you want to buy the spell 'Poison Wall' for %P gold?", Topic=3 +Druid,"explosion" -> String="Explosion", Price=1800, "Do you want to buy the spell 'Explosion' for %P gold?", Topic=3 +Druid,"fire","wall" -> String="Fire Wall", Price=2000, "Do you want to buy the spell 'Fire Wall' for %P gold?", Topic=3 +Druid,"Invisible" -> String="Invisible", Price=2000, "Do you want to buy the spell 'Invisible' for %P gold?", Topic=3 +Druid,"summon","creature" -> String="Summon Creature", Price=2000, "Do you want to buy the spell 'Summon Creature' for %P gold?", Topic=3 +Druid,"energy","wall" -> String="Energy Wall", Price=2500, "Do you want to buy the spell 'Energy Wall' for %P gold?", Topic=3 + +Topic=2,"8$" -> "For level 8 I have 'Find Person' and 'Light'.", Topic=2 +Topic=2,"9$" -> "For level 9 I have 'Light Healing'.", Topic=2 +Topic=2,"10$" -> "For level 10 I have 'Antidote'.", Topic=2 +Topic=2,"11$" -> "For level 11 I have 'Intense Healing'.", Topic=2 +Topic=2,"13$" -> "For level 13 I have 'Great Light'.", Topic=2 +Topic=2,"14$" -> "For level 14 I have 'Food', 'Poison Field' and 'Magic Shield'.", Topic=2 +Topic=2,"15$" -> "For level 15 I have 'Fire Field', 'Intense Healing Rune', 'Antidote Rune' and 'Light Magic Missile'.", Topic=2 +Topic=2,"16$" -> "For level 16 I have 'Convince Creature'.", Topic=2 +Topic=2,"17$" -> "For level 17 I have 'Fireball' and 'Destroy Field'.", Topic=2 +Topic=2,"18$" -> "For level 18 I have 'Energy Field'.", Topic=2 +Topic=2,"20$" -> "For level 20 I have 'Ultimate Healing'.", Topic=2 +Topic=2,"23$" -> "For level 23 I have 'Great Fireball' and 'Creature Illusion'.", Topic=2 +Topic=2,"24$" -> "For level 24 I have 'Ultimate Healing Rune'.", Topic=2 +Topic=2,"25$" -> "For level 25 I have 'Summon Creature' and 'Heavy Magic Missile'.", Topic=2 +Topic=2,"27$" -> "For level 27 I have 'Firebomb' and 'Chameleon'.", Topic=2 +Topic=2,"29$" -> "For level 29 I have 'Poison Wall'.", Topic=2 +Topic=2,"31$" -> "For level 31 I have 'Explosion'.", Topic=2 +Topic=2,"33$" -> "For level 33 I have 'Fire Wall'.", Topic=2 +Topic=2,"35$" -> "For level 35 I have 'Invisible'.", Topic=2 +Topic=2,"41$" -> "For level 41 I have 'Energy Wall'.", Topic=2 + +Topic=2 -> "Sorry, I have only spells for level 8 to 11, 13 to 18, 20, 23 to 25 as well as for the levels 27, 29, 31, 33, 35 and 41.", Topic=2 + + +Topic=3,"yes",SpellKnown(String)=1 -> "You already know how to cast this spell." +Topic=3,"yes",Level Amount=SpellLevel(String), "You have to be level %A to learn this spell." +Topic=3,"yes",CountMoney "Sorry, you do not have enough gold." +Topic=3,"yes" -> "Here you are. Look in your spellbook for the pronunciation of this spell.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=3 -> "Maybe next time." +} diff --git a/data/npc/uzgod.npc b/data/npc/uzgod.npc new file mode 100644 index 0000000..a5baa6d --- /dev/null +++ b/data/npc/uzgod.npc @@ -0,0 +1,150 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uzgod.npc: Datenbank fuer den Waffenhaendler Uzgod + +Name = "Uzgod" +Outfit = (160,77-79-56-115) +Home = [32664,31894,9] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N! Wanna weapon, eh?" +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Silence %N, Me busy! Wait!", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Guut bye. Coming back soon." + +"bye" -> "Guut bye. Coming back soon.", Idle +"farewell" -> * +"job" -> "Me a blacksmith is, an' weapons me sell. You want buy weapons?" +"shop" -> * +"name" -> "Me is Uzgod Hammerslammer, son of Fire, from the Savage Axes. You can say you to me." +"time" -> "Time is %T now." +"help" -> "You can buy the weapons me maked or sell weapons you have, jawoll." +"monster" -> "Me make often hunt on big nasties. Me small, but very big muscles me have, jawoll." +"dungeon" -> "We no dungeon need. We prison isle have." +"prison" -> "Bad ones locked up there. Never come out again there, jawoll." +"mines" -> "Me hacking and smashing rocks as me was little dwarf, jawoll." +"excalibug" -> "You want sell me excalibug for 1000 platinum coins and an enchanted armor?", Topic=3 +"thanks" -> "Me enjoy doing that." +"thank","you" -> * + +"spear" -> Type=3277, Amount=1, Price=10, "You will buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "You will buy a rapier for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "You will buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "You will buy a sword for %P gold?", Topic=1 +"battle","axe" -> Type=3266, Amount=1, Price=235, "You will buy a battle axe for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "You will buy an axe for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "You will buy a battle hammer for %P gold?", Topic=1 +"morning","star" -> Type=3282, Amount=1, Price=430, "You will buy a morning star for %P gold?", Topic=1 +"two","handed","sword" -> Type=3265, Amount=1, Price=950, "You will buy a two handed sword for %P gold?", Topic=1 +"club" -> Type=3270, Amount=1, Price=5, "You will buy a club for %P gold?", Topic=1 +"dagger" -> Type=3267, Amount=1, Price=5, "You will buy a dagger for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "You will buy a mace for %P gold?", Topic=1 +"throwing","knife" -> Type=3298, Amount=1, Price=25, "Do you want to buy a throwing knife for %P gold?", Topic=1 + +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "You will buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "You will buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "You will buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "You will buy %A swords for %P gold?", Topic=1 +%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=235*%1, "You will buy %A battle axes for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "You will buy %A axes for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "You will buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=430*%1, "You will buy %A morning stars for %P gold?", Topic=1 +%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=950*%1, "You will buy %A two handed swords for %P gold?", Topic=1 +%1,1<%1,"club" -> Type=3270, Amount=%1, Price=5*%1, "You will buy %A clubs for %P gold?", Topic=1 +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "You will buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "You will buy %A maces for %P gold?", Topic=1 +%1,1<%1,"throwing","kni" -> Type=3298, Amount=%1, Price=25*%1, "Do you want to buy %A throwing knives for %P gold?", Topic=1 + +"sell","mace" -> Type=3286, Amount=1, Price=23, "You want sell me a mace for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=1, "You want sell me a dagger for %P gold?", Topic=2 +"sell","carlin","sword" -> Type=3283, Amount=1, Price=118, "You want sell me a carlin sword for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=3, "You want sell me a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=5, "You want sell me a sabre for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=15, "You want sell me a sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=75, "You want sell me a battle axe for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=50, "You want sell me a battle hammer for %P gold?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=100, "You want sell me a morning star for %P gold?", Topic=2 +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=190, "You want sell me a two handed sword for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=310, "You want sell me a halberd for %P gold?", Topic=2 +"sell","double","axe" -> Type=3275, Amount=1, Price=260, "You want sell me a double axe for %P gold?", Topic=2 +"sell","war","hammer" -> Type=3279, Amount=1, Price=470, "You want sell me a war hammer for %P gold?", Topic=2 +"sell","longsword" -> Type=3285, Amount=1, Price=51, "You want sell me a longsword for %P gold?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=225, "You want sell me a spike sword for %P gold?", Topic=2 +"sell","fire","sword" -> Type=3280, Amount=1, Price=1000, "You want sell me a fire sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=23*%1, "You want sell me %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=1*%1, "You want sell me %A daggers for %P gold?", Topic=2 +"sell",%1,1<%1,"carlin","sword" -> Type=3283, Amount=%1, Price=118*%1, "You want sell me %A carlin swords for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=1*%1, "Hoho, you me given wood for fireplace? Giving you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=3*%1, "You want sell me %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=5*%1, "You want sell me %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=15*%1, "You want sell me %A swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=75*%1, "You want sell me %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=50*%1, "You want sell me %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=100*%1, "You want sell me %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=190*%1, "You want sell me %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=310*%1, "You want sell me %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"double","axe" -> Type=3275, Amount=%1, Price=260*%1, "You want sell me %A double axes for %P gold?", Topic=2 +"sell",%1,1<%1,"war","hammer" -> Type=3279, Amount=%1, Price=470*%1, "You want sell me %A war hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"longsword" -> Type=3285, Amount=%1, Price=51*%1, "You want sell me %A longswords for %P gold?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=225*%1, "You want sell me %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"fire","sword" -> Type=3280, Amount=%1, Price=1000*%1, "You want sell me %A fire swords for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Me thank you. Here is your stuff.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "No gold, no deal, jawoll." +Topic=1 -> "I sorry you not buy." + +Topic=2,"yes",Count(Type)>=Amount -> "Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You not have it." +Topic=2,"yes",Amount>1 -> "You not have so many." +Topic=2 -> "You my sorry not to sell." + +topic=3,"yes" -> "Stop make fun of old dwarf, you not having it!" +topic=3,"no" -> "Me wouldn't sell it, too." +topic=3 -> "You joke with me!" + +"do","you","sell" -> "What you need? Me just the weapons sell." +"do","you","have" -> * +"light" -> "Me having clubs, daggers, spears, axes, swords, maces, rapiers, and sabres. What is your choice?" +"heavy" -> "Me having the best two handed swords in tibia. I also sell battle hammers. What is your choice?" +"offer" -> "Me offer you light an' heavy weapons." +"weapon" -> * +"helmet" -> "Me just sell weapons." +"armor" -> * +"shield" -> * + +"pickaxe",QuestValue(325)=2,QuestValue(326)=0 -> "Get me brooch and me get you pickaxe. Look for keys that bringing you to dwarven prison and get brooch." +"pickaxe",QuestValue(325)=1 -> "True dwarven pickaxes having to be maded by true weaponsmith! You wanting to get pickaxe for explorer society?", topic=4 +"pickaxe" -> "True dwarven pickaxes having to be maded by true weaponsmith! Me order book full though." + +topic=4,"yes" -> "Me order book quite full is. But telling you what: You getting me something me lost and Uzgod seeing that your pickaxe comes first. Jawoll! You interested?", topic=5 +topic=4,"no" -> "Stop make fun of old dwarf." +topic=4 -> "You joke with me!" + +topic=5,"yes" -> "Good good. You listening: Me was stolen valuable heirloom. Brooch from my family. Good thing is criminal was caught. Bad thing is, criminal now in dwarven prison of dwacatra is and must have taken brooch with him ...","To get into dwacatra you having to get several keys. Each key opening way to other key until you get key to dwarven prison ...","Last key should be in the generals quarter near armory. Only General might have key to enter there too. But me not knowing how to enter Generals private room at barracks. You looking on your own ...","When got key, then you going down to dwarven prison and getting me that brooch. Tell me that you got brooch when having it.",SetQuestValue(325,2) +topic=5 -> "Then you trying again in five years or so. Maybe pickaxe ready then." +"pickaxe",QuestValue(325)=2,QuestValue(326)=1 -> Type=4834, Amount=1,"You got me brooch?",topic=6 +"brooch",QuestValue(325)=2,QuestValue(326)=1 -> * + +topic=6,"yes",Count(Type)>=Amount -> "Thanking you for brooch. Me guessing you now want your pickaxe?", Delete(Type),Type=4845, Amount=1,SetQuestValue(325,3), topic=7 +topic=6,"yes",Count(Type) "Stop make fun of old dwarf." +topic=6,"no" -> "Stop make fun of old dwarf." +topic=6 -> "You joke with me!" + +"pickaxe",QuestValue(325)=3,QuestValue(326)=1 -> Type=4845, Amount=1,"You want you pickaxe?",topic=7 + + +topic=7,"yes" -> "Here you have it.", Create(Type),SetQuestValue(325,4) +topic=7,"no" -> "Stop make fun of old dwarf." +topic=7 -> "You joke with me!" + + +} \ No newline at end of file diff --git a/data/npc/uzon.npc b/data/npc/uzon.npc new file mode 100644 index 0000000..db9de97 --- /dev/null +++ b/data/npc/uzon.npc @@ -0,0 +1,66 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# uzon: Datenbank für den Teppichpiloten Uzon auf den Femor Hills + +Name = "Uzon" +Outfit = (130,95-5-18-76) +Home = [32537,31836,4] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Daraman's blessings, traveller %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Hastiness is not the way of the people of Darama, %N. Give me the time I need here.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Daraman's blessings." + +"bye" -> "Daraman's blessings", Idle +"name" -> "I am known as Uzon Ibn Kalith." +"job" -> "I am a licensed Darashian carpetpilot. I can bring you to Darashia or Edron." +"time" -> "It's %T right now. The next flight is scheduled soon." +"caliph" -> "The caliph welcomes travellers to his land." +"kazzan" -> * +"daraman" -> "Oh, there is so much to tell about Daraman. You better travel to Darama to learn about his teachings." +"ferumbras" -> "I would never transport this one." +"drefia" -> "So you heared about haunted Drefia? Many adventures travel there to test their skills against the undead: vampires, mummies, and ghosts." +"excalibug" -> "Some people claim it is hidden somewhere under the endless sands of the devourer desert in Darama." +"thais" -> "Thais is noisy and overcroweded. That's why I like Darashia more." +"tibia" -> "I have seen almost every place on the continent." +"continent" -> "I could retell the tales of my travels for hours. Sadly another flight is scheduled soon." +"carlin" -> "Just another Thais but with women to lead them." +"flying","carpet" -> "You can buy flying carpets only in Darashia." +"fly" -> "I transport travellers to the continent of Darama for a small fee. So many want to see the wonders of the desert and learn the secrets of Darama." +"news" -> "I heard too many news to recall them all." +"rumors" -> * + +"passage" -> "I can fly you to Darashia on Darama or Edron if you like. Where do you want to go?" +"transport" -> * +"ride" -> * +"trip" -> * + +"darashia" -> Price=60, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama" -> * +"edron" -> Price=60, "Do you want to get a ride to Edron for %P gold?", Topic=2 + +"darashia",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to Darashia on Darama for %P gold?", Topic=1 +"darama",QuestValue(250)>2 -> * +"edron",QuestValue(250)>2 -> Price=50, "Do you want to get a ride to Edron for %P gold?", Topic=2 + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" +Topic=2,"yes",PZBlock,! -> * + +Topic=1,"yes",Premium,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33269,32441,6), EffectOpp(11) +Topic=1,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to enter Darama." +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "You shouldn't miss the experience." + +# für postquest +Topic=2,"yes",Premium, QuestValue(227)=2,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11),SetQuestValue(227,3) + + +Topic=2,"yes",Premium,CountMoney>=Price -> "Hold on!", DeleteMoney, Idle, EffectOpp(11), Teleport(33193,31784,3), EffectOpp(11) +Topic=2,"yes",CountMoney>=Price -> "I'm sorry, but you need a premium account in order to enter Edron." +Topic=2,"yes" -> "You don't have enough money." +Topic=2 -> "You shouldn't miss the experience." +} diff --git a/data/npc/velvet.npc b/data/npc/velvet.npc new file mode 100644 index 0000000..3816d91 --- /dev/null +++ b/data/npc/velvet.npc @@ -0,0 +1,34 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# velvet.npc: Möbelverkäuferin Velvet in Venore + +Name = "Velvet" +Outfit = (136,59-96-115-95) +Home = [32943,32104,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to our shop, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I am Velvet. How can I help you?" +"job" -> "I'm working here in this shop. Are you interested in any of our goods?" +"time",male -> "It's %T, sire." +"time",female -> "It's %T, my lady." + +"offer" -> "I sell pillows and tapestries." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +} diff --git a/data/npc/vera.npc b/data/npc/vera.npc new file mode 100644 index 0000000..4f7a546 --- /dev/null +++ b/data/npc/vera.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# vera.npc: Möbelverkäuferin Vera auf Senja + +Name = "Vera" +Outfit = (136,59-96-115-95) +Home = [32138,31672,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need some equipment for your house?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Vera. I sell furniture and equipment." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/vladruc.npc b/data/npc/vladruc.npc new file mode 100644 index 0000000..be3cc75 --- /dev/null +++ b/data/npc/vladruc.npc @@ -0,0 +1,55 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Vladruc.npc: Datenbank für Vladruc Urghain, den Besitzer des magischen Markts + +Name = "Vladruc" +Outfit = (68,0-0-0-0) +Home = [32976,32079,5] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",Count(3083)>0,! -> "Chhhh ... Sorry, I'm busy. ", Idle +ADDRESS,"hi$",Count(3083)>0,! -> * +ADDRESS,"hello$",! -> "I am Vladruc Urghain and welcome you, %N, to my house." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",Count(3083)>0,! -> "Sorry, I'm busy." +BUSY,"hi$",Count(3083)>0,! -> * +BUSY,"hello$",! -> "Please be seated and wait, %N." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Go safely, and leave something of the happiness you bring!" + +"bye" -> "Go safely, and leave something of the happiness you bring!", Idle +"farewell" -> * +"name" -> "I am Vladruc Urghain. Welcome to my house!" +"job" -> "I am a humble merchant of little importance to the beautiful Venore." +"thais" -> "Sadly only through books I have come to know your great Thais, and to know her is to love her." +"books" -> "These companions have been good friends and teachers to me." +"adventure" -> "The time I sought out adventure is long gone indeed." +"Tibia" -> "What a wonderful world we do live in ... so full of life." +"shop" -> "Ah, feel free to browse and buy in my humble shop below." +"market" -> * +"time" -> "It is %T." +"king" -> "I am of noble blood myself. I have been so long master that none other should be master of me." +"tibianus" -> * +"venore" -> "Our ways are not your ways, and there shall be to you many strange things." +"army" -> "The Thaian garrsion serves its purpose very well." +"ferumbras" -> "You think he is of ancient evil? Little you know about ancientness or evilness." +"excalibug" -> "A terrifying weapon if it does exist at all." +"news" -> "I am a reclusive person and learn little of the local gossip of the peasants." +"help" -> "I am sorry, but I can't be of much assistance to you." +"monster" -> "Oh yes, the children of the night ... you dwellers in the city cannot enter into the feelings of the hunter." +"dungeon" -> "Such lovely places, unjustly shunned by the people." +"vampire" -> "Please don't talk about such creatures. You are scaring me." +"thanks" -> "That's nothing worth to be mentioned." +"thank","you" -> * + +"offer" -> "Please check my humble market downstairs for the wares that are offered." +"magic" -> "Magic is a tool to be mastered." +"spells" -> "I know a spell or two. You might want to buy some spells downstairs in the market." +"alchemy" -> "You can buy some potions downstairs." +"blood" -> "I like blood ... only the color, that is, of course ... " +"undead" -> "It is not dead, which can eternal lie, and in strange aeons, even death may die." +"necroman" -> "Death is the final frontier. Necromancers boldly go, where no one has gone before." +"coffin" -> "The final restingplace for all of us, isn't it?" +} diff --git a/data/npc/wally.npc b/data/npc/wally.npc new file mode 100644 index 0000000..350e764 --- /dev/null +++ b/data/npc/wally.npc @@ -0,0 +1,77 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wally.npc: Datenbank für Wally den Postbeamten in der Postlergilde + +Name = "Wally" +Outfit = (129,96-113-95-115) +Home = [32566,32019,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Be greeted, traveller." +ADDRESS,"hi$",! -> * + +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait in line." +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude!" + +"bye" -> "It was a pleasure to help you.", Idle +"farewell" -> * + +@"gen-post.ndb" + +"name" -> "I'm Wally the post officer." +"job" -> "I am working here at the post office for Kevin." +"kevin" -> "Oh, our boss is upstairs. Better only disturb him with important issues though." +"postner" -> * +"postmasters","guild" -> "We are an organization of importance for the whole of Tibia. Even kings tremble before our might." +"join" -> "You have to talk with the exalted archpostman Kevin Postner if you want to join our prestigeous guild." +"markwin" -> "This minotaur is quite moody. Better make sure not to anger him. He's likely to call for his guards as soon as he notices a stranger, so you are on tough luck if you ever find him." +"santa","claus" -> "This old whitebeard lives on some hill on the western coast of Vega as far as I know." +"brassacres" -> "This guy might be hard to find. Hes likely disguising himself. If you see someone suspicious, try to ask other people who are around about him. That might give you some clue." +"mission" -> "Ask Mr. Postner about your current missions." + +"ben" -> "Old Ben lost some of his marbles in some battle long ago. He is still a quite capable postman though ... on second thought thats some disturbing fact." +"lokur" -> "Dwarfs make quite good postmen. They are stubborn, strong and ... sturdy. Its a waste that he prefers a job behind the counter." +"dove" -> "Dove is as good as a dozend pigeons. He He He." +"olrik" -> "This Olrik was made postman only for convenience. He is quite aware that his attitude and affiliation with the thaian government makes it impossible for him to rise in rank. This leads only to him behaving even worse tough." +"liane" -> "Although I never met her in person we became penpals over the time." +"wally" -> "Yes, thats me, Wally!" + +"advancement" -> "The exalted archpostman Kevin Postner alone decides about advancement of our members. All ranks come with certain privileges." +"privileges" -> "Our privileges are top secret, other people envy us enough already for beeing members in this splendid guild." + +"uniform" -> "We could badly need new uniforms." +"uniform",QuestValue(234)>6 -> "Oh thank you, this new uniform I just got suits me well!" +"waldo",QuestValue(249)<4 -> "Waldo is an explorer. We allways feared he might one day take a risk too great for him. I hope he is ok though." +"waldo",QuestValue(249)=4 -> "To hear about Waldos death strikes my heart with grief. We did not only loose our greatest explorer but a dear friend." + +"dress","pattern" -> "I vaguely remember the last dress pattern of our uniforms was dependend on certain key elements. It had some technical gadgets, a special smell and was uniquely colored." +"crowbar" -> "Most general stores should sell crowbars. I think the store in Edron sells some for instance." +"hint" -> "I can't help you much with your missions. Of course we tell you everything we know and do't make our missions needlesly difficult." +"headquarter" -> "Its humble and practical. Considering we have bases all over the known world we don't need a bigger base anyways. On the other hand Mr. Postner is dreaming about a postman academy now and then." +"bones" -> "If I would be looking for bones I'd inspect some skeletons ... If I weren't so affraid of them that is." +"banana","skin" -> "Uh? How disgusting. Look for this rubbish in some places where waste is dumped .. and don't ever tell me what you need it for." +"fur" -> "As far as I heard some of the minor orcs carry a pice of fur as a fetish or lucky charm with them." +"moldy","cheese" -> "What a disgusting taste you have. Like those Goblins who carry this stuff with them." +"noodles" -> "This dog is his majestys most priced possesion and heavily guarded. Anger the dog and you anger the king." +"thais" -> "One of the oldest holdings of humanity that still exist and the heart of the biggest kingdom in the known world." +"carlin" -> "Carlin is an upcoming power in theese days. Albeit its ambitions it still dwarfs the old kingdom of thais in power and influence." +"venore" -> "I think no longer is king Tibianus reigning this city, nor are the merchants ruling it, regardless what they might think. The true monarch before whom all there bow is the money." +"edron" -> "I gues Edron isn't the source of wealth and rescources as the thaians hoped. The defection of those knights did cause the expansion and exploitation there to an halt." +"defection" -> "I know nothing special about that story. I only heared that a good part of the knightly order the king sent there succumbed to their lust for wealth and power and turned against their swordbrethren." +"darama" -> "A far away place with strange customs and an even stranger philosophy. One day I might travel there to see it on my own." +"darashia" -> * +"ab'dendriel" -> "Elven volunteers for becoming a postofficer are quite rare. We had to rely on a human living there to ensure our postsystems function. Most elvish members of the guild prefer to work as courriers." +"elf" -> * +"elven" -> * +"Kazordoon" -> "The city of the dwarfs is a bit hidden and new postoficers often get lost while looking for it. Just look for a hidden passage to a western valley in the mountaion called the big old one." +"dwarf" -> * +"dwarves" -> * +"dwarfs" -> * +"big","old","one" -> "Its a huge Mountain, north of here, just across the river." +"posthorn" -> "A posthorn is a postmens bride ... or a postwomans husband. The only true friend a lonly postofficer has in the foregin lands and dangerous places he has to visit." +"cap$" -> "A cap is what shows you are a postofficer. But your heart and your state of mind are what you makes a postofficer." +"mailbox" -> "Our mailboxes are quite reliable but know and then one has to be fixed. Especually in the more rough climates." +} diff --git a/data/npc/walter.npc b/data/npc/walter.npc new file mode 100644 index 0000000..ee437ac --- /dev/null +++ b/data/npc/walter.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# walter.npc: Datenbank für die Stadtwache am Südtor + +Name = "Walter, the guard" +Outfit = (131,19-19-19-19) +Home = [32338,32278,7] +Radius = 2 + +Behaviour = { +@"guards-thais.ndb" +} diff --git a/data/npc/warbert.npc b/data/npc/warbert.npc new file mode 100644 index 0000000..0140799 --- /dev/null +++ b/data/npc/warbert.npc @@ -0,0 +1,11 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# warbert.npc: Datenbank für eine Stadtwache in Venore + +Name = "Warbert" +Outfit = (131,113-113-113-115) +Home = [32935,32141,6] +Radius = 10 + +Behaviour = { +@"guards-venore.ndb" +} diff --git a/data/npc/willard.npc b/data/npc/willard.npc new file mode 100644 index 0000000..f75039c --- /dev/null +++ b/data/npc/willard.npc @@ -0,0 +1,175 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# willard.npc: Datenbank für den Schmied Willard + +Name = "Willard" +Outfit = (131,58-104-19-116) +Home = [33214,31793,6] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings and Banor be with you, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I'm ready soon, %N. Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Farewell.", Idle +"name" -> "I am Willard, the smith." +"job" -> "I am the blacksmith of castle Bloodrock." +"time" -> "Now it is %T." +"king" -> "Hail to the King! He's our benevolent protector." +"tibianus" -> * +"army" -> "I supply them with all they need." +"ferumbras" -> "I would be honored if it's one of my blades that one day delivers him his punishment." +"excalibug" -> "Adventurers search for this blade all over the world. Even here." +"thais" -> "A fine city, but I love the peace of Edron more." +"tibia" -> "It's my dream that one day the whole world will profit from the Thaian governance." +"carlin" -> "If the king sees the time is right, he will certainly start a campaign to reclaim what belongs to Thais." +"edron" -> "Edron is a fine city to live in." +"news" -> "Rumors are too sinister things that a true warrior would care for them." +"rumors" -> * + +"buy" -> "What do you need? I sell weapons, armors, helmets, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> "My offers are weapons, ammunition, armors, helmets, legs, and shields." +"weapon" -> "I have hand axes, axes, barbarian axes, spears, maces, clerical maces, battle hammers, swords, rapiers, daggers, sabres, bows, and crossbows. What's your choice?" +"ammunition" -> "I have arrows for bows and bolts for crossbows. What do you want?" +"helmet" -> "I am selling leather helmets and chain helmets. What do you want?" +"armor" -> "I am selling leather, chain, and brass armor. What do you need?" +"shield" -> "I am selling wooden shields, steel shields, and viking shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"dagger" -> Type=3267, Amount=1, Price=5, "Do you want to buy a dagger for %P gold?", Topic=1 +"hand","axe" -> Type=3268, Amount=1, Price=8, "Do you want to buy a hand axe for %P gold?", Topic=1 +"spear" -> Type=3277, Amount=1, Price=10, "Do you want to buy a spear for %P gold?", Topic=1 +"rapier" -> Type=3272, Amount=1, Price=15, "Do you want to buy a rapier for %P gold?", Topic=1 +"axe" -> Type=3274, Amount=1, Price=20, "Do you want to buy an axe for %P gold?", Topic=1 +"barbar","axe" -> Type=3317, Amount=1, Price=590, "Do you want to buy a barbarian axe for %P gold?", Topic=1 +"sabre" -> Type=3273, Amount=1, Price=35, "Do you want to buy a sabre for %P gold?", Topic=1 +"sword" -> Type=3264, Amount=1, Price=85, "Do you want to buy a sword for %P gold?", Topic=1 +"mace" -> Type=3286, Amount=1, Price=90, "Do you want to buy a mace for %P gold?", Topic=1 +"cleric","mace" -> Type=3311, Amount=1, Price=540, "Do you want to buy a clerical mace for %P gold?", Topic=1 +"battle","hammer" -> Type=3305, Amount=1, Price=350, "Do you want to buy a battle hammer for %P gold?", Topic=1 +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrow for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolt for %P gold?", Topic=1 +"throwing","star" -> Type=3287, Amount=1, Price=50, "Do you want to buy a throwing star for %P gold?", Topic=1 + + +%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=5*%1, "Do you want to buy %A daggers for %P gold?", Topic=1 +%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=8*%1, "Do you want to buy %A hand axes for %P gold?", Topic=1 +%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=10*%1, "Do you want to buy %A spears for %P gold?", Topic=1 +%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=15*%1, "Do you want to buy %A rapiers for %P gold?", Topic=1 +%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=20*%1, "Do you want to buy %A axes for %P gold?", Topic=1 +%1,1<%1,"barbar","axe" -> Type=3317, Amount=%1, Price=590*%1, "Do you want to buy %A barbarian axes for %P gold?", Topic=1 +%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=35*%1, "Do you want to buy %A sabres for %P gold?", Topic=1 +%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=85*%1, "Do you want to buy %A swords for %P gold?", Topic=1 +%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=90*%1, "Do you want to buy %A maces for %P gold?", Topic=1 +%1,1<%1,"cleric","mace" -> Type=3311, Amount=%1, Price=540*%1, "Do you want to buy %A clerical maces for %P gold?", Topic=1 +%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=350*%1, "Do you want to buy %A battle hammers for %P gold?", Topic=1 +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 +%1,1<%1,"throwing","star" -> Type=3287, Amount=%1, Price=50*%1, "Do you want to buy %A throwing stars for %P gold?", Topic=1 + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"leather","armor" -> Type=3361, Amount=1, Price=35, "Do you want to buy a leather armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"leather","helmet" -> Type=3355, Amount=1, Price=12, "Do you want to buy a leather helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"viking","shield" -> Type=3431, Amount=1, Price=260, "Do you want to buy a viking shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=35*%1, "Do you want to buy %A leather armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=12*%1, "Do you want to buy %A leather helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=260*%1, "Do you want to buy %A viking shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","two","handed","sword" -> Type=3265, Amount=1, Price=450, "Do you want to sell a two handed sword for %P gold?", Topic=2 +"sell","battle","axe" -> Type=3266, Amount=1, Price=80, "Do you want to sell a battle axe for %P gold?", Topic=2 +"sell","dagger" -> Type=3267, Amount=1, Price=2, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","hand","axe" -> Type=3268, Amount=1, Price=4, "Do you want to sell a hand axe for %P gold?", Topic=2 +"sell","halberd" -> Type=3269, Amount=1, Price=400, "Do you want to sell a halberd for %P gold?", Topic=2 +"sell","club" -> Type=3270, Amount=1, Price=1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell","spike","sword" -> Type=3271, Amount=1, Price=240, "Do you want to sell a spike sword for %P gold?", Topic=2 +"sell","rapier" -> Type=3272, Amount=1, Price=5, "Do you want to sell a rapier for %P gold?", Topic=2 +"sell","sabre" -> Type=3273, Amount=1, Price=12, "Do you want to sell a sabre for %P gold?", Topic=2 +"sell","axe" -> Type=3274, Amount=1, Price=7, "Do you want to sell an axe for %P gold?", Topic=2 +"sell","spear" -> Type=3277, Amount=1, Price=3, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell","morning","star" -> Type=3282, Amount=1, Price=90, "Do you want to sell a morning star for %P gold?", Topic=2 +"sell","mace" -> Type=3286, Amount=1, Price=30, "Do you want to sell a mace for %P gold?", Topic=2 +"sell","short","sword" -> Type=3294, Amount=1, Price=10, "Do you want to sell a short sword for %P gold?", Topic=2 +"sell","battle","hammer" -> Type=3305, Amount=1, Price=120, "Do you want to sell a battle hammer for %P gold?", Topic=2 +"sell","cleric","mace" -> Type=3311, Amount=1, Price=170, "Do you want to sell a clerical mace for %P gold?", Topic=2 +"sell","barbar","axe" -> Type=3317, Amount=1, Price=185, "Do you want to sell a barbarian axe for %P gold?", Topic=2 +"sell","sword" -> Type=3264, Amount=1, Price=25, "Do you want to sell a sword for %P gold?", Topic=2 + +"sell",%1,1<%1,"two","handed","sword" -> Type=3265, Amount=%1, Price=450*%1, "Do you want to sell %A two handed swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","axe" -> Type=3266, Amount=%1, Price=80*%1, "Do you want to sell %A battle axes for %P gold?", Topic=2 +"sell",%1,1<%1,"dagger" -> Type=3267, Amount=%1, Price=2*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"hand","axe" -> Type=3268, Amount=%1, Price=4*%1, "Do you want to sell %A hand axes for %P gold?", Topic=2 +"sell",%1,1<%1,"halberd" -> Type=3269, Amount=%1, Price=400*%1, "Do you want to sell %A halberds for %P gold?", Topic=2 +"sell",%1,1<%1,"club" -> Type=3270, Amount=%1, Price=1*%1, "Are you sure, you want to sell this garbage? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"spike","sword" -> Type=3271, Amount=%1, Price=240*%1, "Do you want to sell %A spike swords for %P gold?", Topic=2 +"sell",%1,1<%1,"rapier" -> Type=3272, Amount=%1, Price=5*%1, "Do you want to sell %A rapiers for %P gold?", Topic=2 +"sell",%1,1<%1,"sabre" -> Type=3273, Amount=%1, Price=12*%1, "Do you want to sell %A sabres for %P gold?", Topic=2 +"sell",%1,1<%1,"axe" -> Type=3274, Amount=%1, Price=7*%1, "Do you want to sell %A axes for %P gold?", Topic=2 +"sell",%1,1<%1,"spear" -> Type=3277, Amount=%1, Price=3*%1, "Do you want to sell this garbage? I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"morning","star" -> Type=3282, Amount=%1, Price=90*%1, "Do you want to sell %A morning stars for %P gold?", Topic=2 +"sell",%1,1<%1,"mace" -> Type=3286, Amount=%1, Price=30*%1, "Do you want to sell %A maces for %P gold?", Topic=2 +"sell",%1,1<%1,"short","sword" -> Type=3294, Amount=%1, Price=10*%1, "Do you want to sell %A short swords for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","hammer" -> Type=3305, Amount=%1, Price=120*%1, "Do you want to sell %A battle hammers for %P gold?", Topic=2 +"sell",%1,1<%1,"cleric","mace" -> Type=3311, Amount=%1, Price=170*%1, "Do you want to sell %A clerical maces for %P gold?", Topic=2 +"sell",%1,1<%1,"barbar","axe" -> Type=3317, Amount=%1, Price=185*%1, "Do you want to sell %A barbarian axes for %P gold?", Topic=2 +"sell",%1,1<%1,"sword" -> Type=3264, Amount=%1, Price=25*%1, "Do you want to sell %A swords for %P gold?", Topic=2 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","iron","helmet" -> Type=3353, Amount=1, Price=150, "Do you want to sell an iron helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 +"sell","viking","shield" -> Type=3431, Amount=1, Price=85, "Do you want to sell a viking shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"iron","helmet" -> Type=3353, Amount=%1, Price=150*%1, "Do you want to sell %A iron helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 +"sell",%1,1<%1,"viking","shield" -> Type=3431, Amount=%1, Price=85*%1, "Do you want to sell %A viking shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/william.npc b/data/npc/william.npc new file mode 100644 index 0000000..f27d86a --- /dev/null +++ b/data/npc/william.npc @@ -0,0 +1,32 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# william.npc: Datenbank für den carlin trunkenbold William + +Name = "William" +Outfit = (128,115-0-67-114) +Home = [32314,31802,8] +Radius = 3 + +Behaviour = { +ADDRESS,"hi$",! -> "Oh, hello! " +ADDRESS,"hello$",! -> * +ADDRESS,! -> Idle +BUSY,"hi$",! -> " Not right now...", Queue +BUSY,"hello$",! -> * +BUSY,! -> NOP +VANISH,! -> " Damn, I am starting to imagine things... again." + +"bye" -> "Bye bye .", Idle +"how","are","you"-> "Thanks I am drunk ." +"sell" -> "Hey! Thats my drink, buy your own!" +"job" -> "I forgot what a job I have." +"karl" -> "A good guy with good beer." +"name" -> "My Name? Uh... Wait! 'Bring down the trash, William you...' William, my name is William!" +"time" -> "Its precisely after ." +"help" -> "I need another drink, then I'll help you. Promise." +"carlin" -> "I whish I'd live in Thais, the city of alcohol." +"thais" -> * +"sewer" -> "The sewers are our last refuge." +"refuge" -> "Yes, refuge from womanhood." +"todd" -> "In Todd we trust! TODD! TODD! TODD!" + +} diff --git a/data/npc/willie.npc b/data/npc/willie.npc new file mode 100644 index 0000000..a58d525 --- /dev/null +++ b/data/npc/willie.npc @@ -0,0 +1,88 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# willie.npc: Datenbank fuer den Farmer Willie + +Name = "Willie" +Outfit = (128,58-63-58-115) +Home = [32047,32204,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hiho %N." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Nah, I am talking. Wait here.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "YOU RUDE $!@##&" + +"bye" -> "Yeah, bye.", Idle +"farewell" -> "Yeah, farewell.", Idle +"how","are","you" -> "Fine enough." +"job" -> "I am a farmer and a cook." +"cook" -> "I try out old and new recipes. You can sell me all food you have." +"recipe" -> "I would love to try a banana-pie. But I lack the bananas. If you get me one, I will reward you." +"name" -> "Willie." +"time" -> "Am I a clock or what?" +"help" -> "Help yourself, I have not stolen my time." +"monster" -> "Are you afraid of monsters ... you baby?" +"dungeon" -> "I have no time for your dungeon nonsense." +"sewer" -> "What about them? Do you live there?" +"god" -> "I am a farmer, not a preacher." +"king" -> "I'm glad that we don't see many officials here." +"obi" -> "This little $&#@& has only #@$*# in his mind. One day I will put a #@$@ in his *@&&#@!" +"seymour" -> "This joke of a man thinks he is sooo important." +"dallheim" -> "Uhm, fine guy I think." +"cipfried" -> "Our little monkey." +"amber" -> "Quite a babe." +"weapon" -> "I'm not in the weapon business, but if you don't stop to harass me, I will put my hayfork in your &$&#$ and *$!&&*# it." +"magic" -> "I am magician in the kitchen." +"spell" -> "I know how to spell and i know how to spit, you little @!#&&. Wanna see?." +"tibia" -> "If I were you, I would stay here." + +"bread" -> Type=3600, Amount=1, Price=3, "Do you want to buy a bread for %P gold?", Topic=1 +"cheese" -> Type=3607, Amount=1, Price=5, "Do you want to buy a cheese for %P gold?", Topic=1 +"meat" -> Type=3577, Amount=1, Price=5, "Do you want to buy meat for %P gold?", Topic=1 +"ham" -> Type=3582, Amount=1, Price=8, "Do you want to buy a ham for %P gold?", Topic=1 + +%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=3*%1, "Do you want to buy %A breads for %P gold?", Topic=1 +%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=5*%1, "Do you want to buy %A cheese for %P gold?", Topic=1 +%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=5*%1, "Do you want to buy %A meat for %P gold?", Topic=1 +%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=8*%1, "Do you want to buy %A hams for %P gold?", Topic=1 + +"offer" -> "I can offer you bread, cheese, ham, or meat." +"food" -> "Are you looking for food? I have bread, cheese, ham, and meat." + +Topic=1,"yes",CountMoney>=Price -> "Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "I am sorry, but you do not have enough gold." +Topic=1 -> "Maybe later." + +"sell","bread" -> Type=3600, Amount=1, Price=1, "So, you want to sell a bread? Hmm, I give you %P gold, ok?", Topic=2 +"sell","cheese" -> Type=3607, Amount=1, Price=2, "So, you want to sell a cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell","meat" -> Type=3577, Amount=1, Price=2, "So, you want to sell meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell","ham" -> Type=3582, Amount=1, Price=4, "So, you want to sell a ham? Hmm, I give you %P gold, ok?", Topic=2 +"sell","salmon" -> Type=3579, Amount=1, Price=2, "So, you want to sell a salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell","fish" -> "Go away with this stinking &*#@@!" +"sell","cherry" -> Type=3590, Amount=1, Price=1, "So, you want to sell a cherry? Hmm, I give you %P gold, ok?", Topic=2 + +"sell",%1,1<%1,"bread" -> Type=3600, Amount=%1, Price=1*%1, "So, you want to sell %A breads? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"cheese" -> Type=3607, Amount=%1, Price=2*%1, "So, you want to sell %A cheese? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"meat" -> Type=3577, Amount=%1, Price=2*%1, "So, you want to sell %A meat? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"ham" -> Type=3582, Amount=%1, Price=4*%1, "So, you want to sell %A hams? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"salmon" -> Type=3579, Amount=%1, Price=2*%1, "So, you want to sell %A salmon? Hmm, I give you %P gold, ok?", Topic=2 +"sell",%1,1<%1,"fish" -> "Go away with this stinking &*#@@!" +"sell",%1,1<%1,"cherr" -> Type=3590, Amount=%1, Price=1*%1, "So, you want to sell %A cherries? Hmm, I give you %P gold, ok?", Topic=2 + +"sell" -> "I sell food of many kinds." +"buy" -> "I buy food of any kind. Since I am a great cook I need much of it." + +Topic=2,"yes",Count(Type)>=Amount -> "Here you are.", Delete(Type), CreateMoney +Topic=2,"yes" -> "You don't have one." +Topic=2,"no" -> "Then not." + +"banana" -> Type=3587, Amount=1, "Have you found a banana for me?", Topic=3 +Topic=3,"yes",Count(Type)>=Amount -> "A banana! Great. Take this shield, so the &#@&* monsters don't beat the &@*&@ out of you.", Delete(Type), Create(3426) +Topic=3,"yes" -> "Hm, you don't have it." +Topic=3,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=3,"no" -> "Too bad." +Topic=3 -> "Too bad." +} diff --git a/data/npc/windtrouser.npc b/data/npc/windtrouser.npc new file mode 100644 index 0000000..e8c195e --- /dev/null +++ b/data/npc/windtrouser.npc @@ -0,0 +1,76 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# windtrouser.npc: Fischer Dalbrect Windtrouser nahe Mönchsinsel + +Name = "Dalbrect" +Outfit = (129,76-97-105-76) +Home = [32211,31756,7] +Radius = 5 + + +Behaviour = { +ADDRESS,"hello$",QuestValue(220)>0,QuestValue(62)=2,! -> "Sorry my friend, the monks don't allow me to talk to you because of your intrusion in their chambers. Unless you plea to the abbot for absolution I can do nothing for you.",Idle +ADDRESS,"hi$",QuestValue(220)>0,QuestValue(62)=2,! -> * + +ADDRESS,"hello$",QuestValue(220)>0,! -> "The monks forbid me to talk to you for you evil deeds.",Idle +ADDRESS,"hi$",QuestValue(220)>0,! -> * + +ADDRESS,"hello$",! -> "Be greeted, traveler." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",QuestValue(220)>0,QuestValue(62)=2,! -> "Sorry %N, the monks don't allow me to talk to you because of your intrusion in their chambers. Unless you plea to the abbot for absolution I can do nothing for you.",Idle +BUSY,"hi$",QuestValue(220)>0,QuestValue(62)=2,! -> * + +BUSY,"hello$",QuestValue(220)>0,! -> "Sorry %N, the monks forbid me to talk to you for you evil deeds.",Idle +BUSY,"hi$",QuestValue(220)>0,! -> * + +BUSY,"hello$",! -> "One moment please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP + +VANISH,! -> "Good bye. You are welcome." + +"bye" -> "Good bye. You are welcome.", Idle +"farewell" -> * +"name" -> "My name is Dalbrect Windtrouser, of the once proud windtrouser family." +"job" -> "I am merely a humble fisher now that nothing is left of my noble legacy." +"captain" -> * +"ship" -> "My ship is my only pride and joy." +"ferry" -> * +"legacy" -> "Once my family was once noble and wealthy, but fate turned against us and threw us into poverty." +"family" -> * +"nobility" -> * +"fate" -> "When Carlin tried to colonize the region now known as the ghostlands, my ancestors put their fortune in that project." +"poverty" -> * +"project" -> "Our family fortune was lost when the colonization of those cursed lands failed. Now nothing is left of our fame or our fortune. If I only had something as a reminder of those better times. " +"ghostlands" -> * + + +"brooch",QuestValue(62)=2,! -> "You have recovered my brooch! I shall forever be in your debt, my friend!" +"brooch" -> "What? You want me to examine a brooch?", Type=3205, Amount=1, topic=3 +topic=3, "yes", Count(Type) "What are you talking about? I am too poor to be interested in jewelry." +topic=3, "yes", Count(Type)>=Amount -> "Can it be? I recognize my family's arms! You have found a treasure indeed! I am poor and all I can offer you is my friendship, but ... please ... give that brooch to me?", topic=2 +topic=3 -> "Then stop being a fool. I am poor and I have to work the whole day through!" + +Topic=2,"yes",Count(Type)>=Amount -> "Thank you! I shall consider you my friend from now on! Just let me know if you need something!", Delete(Type), SetQuestValue(62,2) +Topic=2,"yes" -> "I should have known better then to ask for an act of kindness in this cruel, selfish, world!" +Topic=2 -> * + + +"trip" -> "I have only sailed to the isle of the kings once or twice. I dare not anger the monks by bringing travellers there without their permission." +"passage" -> * +"carlin" -> "To think my family used to belong to the local nobility! And now those arrogant women are in charge!" +"island" -> "The only isle I visit regularly is the isle of the kings. I bring food and the occasional visitor to the monastery." +"monastery" -> "The monks are not exactly fond of visitors, so I rarely take somebody there without their permission." +"white","raven" -> "I think that is the name both of the monastery and of the monks' order." +"passage",QuestValue(62)=1 -> Price=20, "Since you have the abbot's permission I can sail you to the isle of the kings for %P gold. Is that ok for you?", Topic=1 +"passage",QuestValue(62)=2 -> Price=10, "Since you are my friend now I will sail you to the isle of the kings for %P gold. Is that okay for you?", Topic=1 +"passage" -> "You do not have the abbot's permission, and I won't risk angering the monks because of some guy I do not even know." + +Topic=1,"yes",PZBlock,! -> "First get rid of those blood stains! You are not going to ruin my vehicle!" + +Topic=1,"yes",CountMoney>=Price -> "Have a nice trip!", DeleteMoney, Idle, EffectOpp(11), Teleport(32190,31957,6), EffectOpp(11) +Topic=1,"yes" -> "You don't have enough money." +Topic=1 -> "Well, I'll be here if you change your mind." + +} diff --git a/data/npc/wyat.npc b/data/npc/wyat.npc new file mode 100644 index 0000000..8fbaeb3 --- /dev/null +++ b/data/npc/wyat.npc @@ -0,0 +1,74 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wyat.npc: Datenbank für den Sheriff von Thais Wyat + +Name = "Wyat" +Outfit = (129,98-96-95-116) +Home = [32325,32276,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hail$",! -> "Salutations!" +ADDRESS,"salutations$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "I am busy!" +BUSY,"hi$",! -> * +BUSY,"hail$",! -> * +BUSY,"salutations$",! -> * +BUSY,! -> NOP +VANISH,! -> "LONG LIVE THE KING!" + +"bye" -> "LONG LIVE THE KING!", Idle +"news" -> "I have no news for the public." +"king" -> "HAIL TO KING TIBIANUS!" +"leader" -> * +"job" -> "I am the sheriff of the Thaian territory." +"how","are","you"-> "I am fine, thanks." +"sell" -> "I am in the safety business." +"army" -> "I usually work with the townguards only." +"guard" -> * +"general" -> "Old Bloodblade does a fine job." +"enemies" -> "Our enemies are numerous and not all are obvious." +"enemy" -> * +"criminal" -> * +"murderer" -> * +"castle" -> "The castle should be relatively safe from criminal transgressions." +"subject" -> "There are certain criminal objects in the population of our town." +"red","guard" -> "Most of the red guards serve as cityguards, some work for the TBI though." +"secret","police"-> "All i can tell you is, that it's known as the TBI." +"tbi$" -> "The Tibian Bureau of Investigation. If you want to know more, ask Chester Kahs about it, but I doubt you'll get any vital information." +"chester" -> "His bureau is at the northgate." +"silver","guard" -> "They are our elite forces." +"city" -> "The city is not as bad as some people might claim, but we certainly have our problems here." +"problem" -> "We will handle each problem with care." +"stutch" -> "A fine warrior, indeed. He is one of the king's bodyguards." +"harsky" -> * +"bozo" -> "He's so funny, I could listen to his jokes for hours." +"sam" -> "Sam, the Thaian smith, is a man of great diligence. Whenever in need of weapons or armor, just ask him." +"weapon" -> * +"armor" -> * +"elane" -> "A woman of great skill and courage. No one deserves the title of a Grandmaster of the Paladins more then her." +"gorn" -> "He was a rowdy in his youth, but now he's a fine citizen as far as I can tell." +"benjamin" -> "The poor fool lost his mind some years ago. It's a good thing they gave him a job in the post office." +"ferumbras" -> "He attacked our town at several occasions but was repelled each time." +"quest" -> "Look up our 'Tibia's most wanted' lists." +"mission" -> * +"god" -> "I am follower of Banor." +"banor" -> "He is the patron of justice and bravery." +"zathroth" -> "Don't mention this name!" +"brog" -> "The more primitive races such as orcs often worship the raging one." +"monster" -> "Thais should be relatively safe from direct assaults of monsters." +"excalibug" -> "If you have any news about the whereabouts of that blade, report it to me." +"rebellion" -> "Luckily that's nothing I have to care about." + +"fuck" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"idiot" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"asshole" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"ass$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"fag$" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"stupid" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"tyrant" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"shit" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +"lunatic" -> "Take this!", Burning(10,1), EffectOpp(5), EffectMe(8) +} diff --git a/data/npc/wyda.npc b/data/npc/wyda.npc new file mode 100644 index 0000000..4df4e8f --- /dev/null +++ b/data/npc/wyda.npc @@ -0,0 +1,121 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# wyda.npc: Datenbank für die Hexe Wyda (Swamp) + +Name = "Wyda" +Outfit = (54,0-0-0-0) +Home = [32726,31980,6] +Radius = 5 + +Behaviour = { +ADDRESS,"hello$",Druid,Count(3065)>=1,! -> "Welcome back, %N. Hey, nice wand you have there!" +ADDRESS,"greet",Druid,Count(3065)>=1,! -> * +ADDRESS,"hello$",Druid,! -> "Welcome to my hut, %N! It's always nice to see a druid here." +ADDRESS,"greet",Druid,! -> * +ADDRESS,"hello$",Sorcerer,! -> "What do you want, %N?" +ADDRESS,"greet",Sorcerer,! -> * +ADDRESS,"hello$",! -> "Good day, %N." +ADDRESS,"greet",! -> * +ADDRESS,"good","day",! -> * +ADDRESS,"hi$",! -> "What? Talking to me, %N?", Idle +ADDRESS,! -> Idle +BUSY,"hello$",! -> "%N, just a moment, please.", Queue +BUSY,"greet",! -> * +BUSY,"good","day",! -> * +BUSY,! -> NOP +VANISH,! -> "Farewell." + +"bye" -> "Good luck on your journeys.", Idle +"farewell" -> * +"see","you" -> * +"job" -> "I am a witch. Didn't you notice?" +"name" -> "My name is Wyda, and what's yours?" +"my","name","is" -> "Nice to meet you." +"time" -> "I think it is the fourth year after Queen Eloise's crowning, but I cannot tell you date or time." + +"sorcerer" -> "Sorcerers have forgotten about the root of all beings: nature." +"druid" -> "Druids are mostly fine people. I'm always happy when I meet one." +"knight" -> "Knights succumb to the blindness of rage and the desire for violence and blood." +"paladin" -> "Paladins can use bows, but not brains." +"queen" -> "Eloise is Queen of Carlin. I don't care about royals much, as long as they don't try to tax me." +"i$","live" -> "That's nice." +"carlin" -> "Carlin is a beautiful town, but far from here. Do you live there?" +"thais" -> "I've heard stories about that city. It's nowhere near here, that's all I can tell you about it." +"stories" -> "Thais is an overcrowded place inhabited by brutal murderers. At least that's what I've been told." +"tibia$" -> "Tibia is the name of our continent." +"tibianus" -> "Haha, that's a stupid name. Who's that?" +"ferumbras" -> * +"king" -> "There are too many royals on this continent if you ask me..." +"evil" -> "Evilness doesn't scare me." +"aureus" -> "Aureus is a good friend who spends much time in this area!" +"bridge" -> "There's a bridge to the west, but it's guarded by dwarfs." +"plains" -> "Many tales exist about some so-called Plains of Havoc. It seems to be a dangerous place." +"havoc" -> * +"help" -> "I can only help with knowledge. What do you want me to tell you about?" +"hunter" -> "To the east, there is a little settlement of hunters. They are cruel humans who attack everything they see." +"buy" -> "I'm currently not selling anything." +"offer" -> * +"sell" -> "There's nothing I need right now, thanks." +"key" -> "I keep my keys where they belong - in my pocket." +"monster" -> "Many creatures live in, around, and beneath the swamp. Be careful!" +"creature" -> * +"swamp" -> "Be careful of the swamp water, it's poisonous!" +"nature" -> "There are many swamp plants, mushrooms, and herbs around here." +"plant" -> "There are many kinds of swamp plants, some can be used for potions, some not." +"potion" -> "The recipe of the potions is one of the witches' secrets!" +"secret" -> * +"recipe" -> * +"sister" -> "Some sisters of mine are having a meeting nearby. Don't disturb them, or they will get angry and attack you." +"witches" -> * +"mushroom" -> "Mushrooms taste good and are useful for potions." +"heal" -> "I do not have any potions for healing available right now." +"giant","spider"-> "Yes, there is such a thing in the east, on a small island. It's very powerful." +"beholder" -> "Beholders? Strange creatures that have mysterious magical abilities." +"slime" -> "There's lots of slime around. It is said that they live from the swamp water." +"god" -> "I believe that nature itself is God." +"magic" -> "The magic of the witches is one of our secrets!" +"spell" -> * +"weatherwax" -> "I think I've heard that name before..." +"ogg" -> * +"voodoo" -> "I don't practice such nonsense, that's just a rumour." +"coffin" -> "That's none of your business." +"dwarf" -> "The little bearded fellows have a town somewhere in the northwest." +"dwarves" -> * +"little","fellows"-> * +"kazordoon" -> "Isn't that the name of the little bearded fellows' town?" +"gold" -> "Money means nothing to me." +"platin" -> * +"cookie" -> "I bake cookies now and then in my spare time." +"orange" -> "I love exotic fruits. I have oranges imported from the south sometimes, but that's very expensive." +"fly","broom",! -> "Haha, no... where did you get that idea? I use it to sweep my platform." +"ride","broom",!-> * +"broom","fly",! -> * +"broom" -> "What about it?" +"platform" -> "This platform and house were built by my mother, long ago." +"mother" -> "Of course my mother was also a witch!" +"crystal","ball"-> "It's a magical item that only witches can use." +"black","knight"-> "A black knight? Black is the color of witches, why whould any knight carry black?" +"earthquake" -> "The earth in this region shakes now and then. Foolish people think that this is because the Gods are angry." + +"become","witch",female,! -> "You can't just become a witch. Either you are or you aren't - and YOU obviously aren't!" +"become","witch",male,! -> "You're a MAN!" +"witch$" -> "Aye, I am a witch." +"man$" -> "There are only female witches." +"men$" -> * + +"power","wand",! -> "The power of the wand can only be used by witches." +"use","wand",! -> * +"what","do","wand",! -> * +"wand" -> "I use a wooden spellwand. Why are you asking?" +"spellwand" -> * + +"quest" -> "A quest? Well, if you're so keen on doing me a favour... Why don't you try to find a blood herb?" +"herbs" -> "The swamp is home to a wide variety of herbs, but the most famous is the blood herb." +"blood","herb$",Count(3734)>=1,! -> "Do you have a blood herb for me?", Topic=1 +"blood","herb$" -> "The blood herb is very rare. This plant would be very useful for me, but I don't know any accessible places to find it." + +Topic=1,"yes",Count(3734)<1,! -> "Well, do you own one or not?" +#Topic=1,"yes",Sorcerer -> Price=400, "Hmm, thanks. Take this.", Delete(3734), CreateMoney +#Topic=1,"yes",Druid -> "Thank you so much! Here, let me give you a reward...", Delete(3734), Create(3065) +#Topic=1,"yes" -> Price=300, "Thank you! Here are some coins for your help.", Delete(3734), CreateMoney +Topic=1,"yes" -> "Thank you so much! Here, let me give you a reward...", Delete(3734), Create(3211) +} diff --git a/data/npc/xed.npc b/data/npc/xed.npc new file mode 100644 index 0000000..f5e3b99 --- /dev/null +++ b/data/npc/xed.npc @@ -0,0 +1,65 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# Xed.npc: Datenbank für den Bogner Xed + +Name = "Xed" +Outfit = (129,78-36-57-97) +Home = [32904,32117,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, %N. Welcome to the distance fighting booth of the Ironhouse." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Goodbye, and may the gods be with you." + +"bye" -> "Goodbye, and may the gods be with you.", Idle +"farewell" -> * +"job" -> "I am the humble supplier for distance fighting weapons of the Ironhouse, owned by Abran Ironeye." +"fletcher" -> * +"name" -> "People call me Xed, but my full name is Xedem." +"time" -> "I don't know, maybe what you really need is a watch." +"hurt" -> "Go to a priest. I am sure they will fix you up." +"Abran","Ironeye" -> "He is the owner of this market, although - just between you and me - I'm not so sure he's honest." +"honest" -> "Well, I overheard the boss discussing some shady deals with a man in a black cloak." +"shady","deals" -> "Something about a sword only great warlords can use and a rare distance fighting item." +"rare","distance" -> "Yes, but I believe this is nothing but lies seeing that there are only a few distance fighting weapons." +"amazons" -> "They are a band or tribe of strange women that have nothing in common with civilized men like me." +"general" -> "You must be talking of the great general Benjamin. He saved the kingdom from ferumbras you know." +"army" -> "We supply the archers of the army with distance fighting weapons." +"ferumbras" -> "I heard rumours somewhere that his father was called Hugo." +"Xed" -> "Yeah, nice name, eh?" +"excalibug" -> "I think that was the sword they were talking about. Said something about a man in Edron that could get it for him." +"news" -> "Some people say Ferumbras isn't really dead. Crazy kids!" +"help" -> "I sell items of the distance type." +"monster" -> "Yeah, these awful beasts. They live in the swamps near the city and in dark dungeons." +"dungeon" -> "Oh, they are all over. You never see more of them than in Kaz, though." +"Kaz" -> "Oh, that's short for Kazordoon." + +"buy" -> "I am selling bows, crossbows, and ammunition. Do you need anything?" +"do","you","sell" -> * +"do","you","have" -> * +"offer" -> * +"goods" -> * +"ammo" -> "Do you need arrows for a bow, or bolts for a crossbow?" +"ammunition" -> * + +"sell","bow" -> "I don't buy used bows." +"sell","crossbow" -> "I don't buy used crossbows." + +"bow" -> Type=3350, Amount=1, Price=400, "Do you want to buy a bow for %P gold?", Topic=1 +"crossbow" -> Type=3349, Amount=1, Price=500, "Do you want to buy a crossbow for %P gold?", Topic=1 +"arrow" -> Type=3447, Amount=1, Price=2, "Do you want to buy an arrows for %P gold?", Topic=1 +"bolt" -> Type=3446, Amount=1, Price=3, "Do you want to buy a bolts for %P gold?", Topic=1 + +%1,1<%1,"bow" -> Type=3350, Amount=%1, Price=400*%1, "Do you want to buy %A bows for %P gold?", Topic=1 +%1,1<%1,"crossbow" -> Type=3349, Amount=%1, Price=500*%1, "Do you want to buy %A crossbows for %P gold?", Topic=1 +%1,1<%1,"arrow" -> Type=3447, Amount=%1, Price=2*%1, "Do you want to buy %A arrows for %P gold?", Topic=1 +%1,1<%1,"bolt" -> Type=3446, Amount=%1, Price=3*%1, "Do you want to buy %A bolts for %P gold?", Topic=1 + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." +} diff --git a/data/npc/xodet.npc b/data/npc/xodet.npc new file mode 100644 index 0000000..078fdce --- /dev/null +++ b/data/npc/xodet.npc @@ -0,0 +1,58 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# xodet.npc: Datenbank für den Magieladen-Besitzer Xodet + +Name = "Xodet" +Outfit = (130,19-86-87-95) +Home = [32399,32222,7] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Oh, please come in, %N. What do you need?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Sorry %N, I am already talking to a customer. Wait a minute, please.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye and come again.", Idle +"farewell" -> * +"name" -> "I'm Xodet, the owner of this shop." +"job" -> "I'm sorcerer and trade with all kinds of magic items." +"sorcerer" -> "There is a sorcerer guild in Thais. Just go in the east of the town, it is easly to find." + +"offer" -> "I'm selling life and mana fluids, runes, wands, rods and spellbooks." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"rune" -> "I sell blank runes and spell runes." +"life","fluid" -> Type=2874, Data=11, Amount=1, Price=60, "Do you want to buy life fluid for %P gold?", Topic=2 +"mana","fluid" -> Type=2874, Data=10, Amount=1, Price=55, "Do you want to buy mana fluid for %P gold?", Topic=2 +"blank","rune" -> Type=3147, Amount=1, Price=10, "Do you want to buy a blank rune for %P gold?", Topic=1 +"spellbook" -> Type=3059, Amount=1, Price=150, "Do you want to buy a spellbook for %P gold?", Topic=1 + +%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=2 +%1,1<%1,"mana","fluid" -> Type=2874, Data=10, Amount=%1, Price=55*%1, "Do you want to buy %A potions of mana fluid for %P gold?", Topic=2 +%1,1<%1,"blank","rune" -> Type=3147, Amount=%1, Price=10*%1, "Do you want to buy %A blank runes for %P gold?", Topic=1 +%1,1<%1,"spellbook" -> Type=3059, Amount=%1, Price=150*%1, "Do you want to buy %A spellbooks for %P gold?", Topic=1 + +"deposit" -> "I will pay you 5 gold for every empty vial. Ok?", Data=0, Topic=3 +"vial" -> * +"flask" -> * + +Topic=1,"yes",CountMoney>=Price -> "Here you are.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Come back, when you have enough money." +Topic=1 -> "Hmm, but next time." + +Topic=2,"yes",CountMoney>=Price -> "Here you are. There is a deposit of 5 gold on the vial.", DeleteMoney, Create(Type) +Topic=2,"yes" -> "Come back, when you have enough money." +Topic=2 -> "Hmm, but next time." + +Topic=3,"yes",Count(2874)>0 -> Amount=Count(2874), Price=Amount*5, "Here you are ... %P gold.", Delete(2874), CreateMoney +Topic=3,"yes" -> "You don't have any empty vials." +Topic=3 -> "Hmm, but please keep Tibia litter free." + +@"gen-t-runes-free-s.ndb" +@"gen-t-wands-free-s.ndb" +} diff --git a/data/npc/yaman.npc b/data/npc/yaman.npc new file mode 100644 index 0000000..82e4602 --- /dev/null +++ b/data/npc/yaman.npc @@ -0,0 +1,161 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yaman.npc: Datenbank für den Efreethändler Yaman (Magische Gegenstände, Efreet) + +Name = "Yaman" +Outfit = (51,0-0-0-0) +Home = [33045,32620,2] +Radius = 1 + +Behaviour = { + +ADDRESS,"hello$",QuestValue(278)=3,! -> "Well, if it isn't a human. Greetings, %N!" +ADDRESS,"hi$",QuestValue(278)=3,! -> * +ADDRESS,"greetings$",QuestValue(278)=3,! -> * +ADDRESS,"djanni'hah$",QuestValue(278)=3,! -> * +ADDRESS,! -> Idle +BUSY,"hello$",QuestValue(278)=3,! -> "One at a time, human. Wait until I have time for you, %N.", Queue +BUSY,"hi$",QuestValue(278)=3,! -> * +BUSY,"greetings$",QuestValue(278)=3,! -> * +BUSY,"djanni'hah$",QuestValue(278)=3,! -> * +BUSY,! -> NOP +VANISH -> "Farewell, human." + +"bye" -> "Goodbye human.", Idle +"farewell" -> * +"name" -> "I'm called Yaman." +"Yaman" -> "That is my name." +"job" -> "I am a trader. Not a popular job around here since it involves dealing with humans, but I don't mind. I rather sit here than have my brain bashed out in this childish war. ...", + "If you have the permission of Malor to trade with us, I will buy all magical equipment you have to offer." +"permission" -> "I am not allowed to buy anything from you unless Malor gave you the permission to trade with us." +"malor" -> "Malor is our leader. He is driven by greed and by ambition. He is clever, though. Personally, I sided with him because Daraman's creed did not appeal to me. ...", + "'Everybody for himself' is my motto. As an Efreet, I can do what I want. No artificial moral restraints. I take what I want." +"efreet" -> "Curious how we have changed physically in the course of time, isn't it? But the fact that we have different skin colours cannot hide the fact that Efreet and Marid are still related by blood." +"marid" -> "I do not hate the Marid - not more than anybody else, anyhow. I joined the Efreet because it seemed the logical thing to do." + +"gabel" -> "Even though Malor would never admit it, Gabel is a strong and charismatic leader. We cannot win this war as long as he is alive." +"king" -> "The djinns do not have kings these days. Gabel renounced his right to the title - a clever move. Malor would love to proclaim himself king, but he does not have the authority to do it. Perhaps one day he will." +"djinn" -> "My race is stuck in a vicious civil war. As long as this war hasn't ended the djinn are too self-absorbed to deal with other races." +"war" -> * + +"mal'ouquah" -> "Mal'ouquah is the name of this place. Malor built it - hence the name. It means 'Malor's rage'." +"ashta'daramai" -> "Ashta'daramai is the name of the Marids' fortress. Gabel tore his former fortress down and erected Ashta'daramai in its place. ...", + "Personally I preferred the older fortress, but it does not make much difference. If our side wins the war it will be torn down anyway." +"human" -> "Humans are selfish, greedy and unnecessarily cruel. In other words, they resemble us in many ways. ...", + "Unlike other Efreet I would not kill a human just for the fun of it. However, I would not hesitate to do it if it seemed profitable. No offence." +"zathroth" -> "The legends say Zathroth abandoned the djinn because they were way too headstrong. I like that story - for two reasons. ...", + "Firstly, it shows that we have independent minds. ...", + "And secondly, it shows that we djinn are free to choose our own destinies. ...", + "No gods, no ties, no rules. We can do as we please and be who we want." +"tibia" -> "This world is our playground." +"daraman" -> "Of course I know Daraman. How could I ever forget him. I was there, remember? ...", + "The man was a dangerous fool. At first I laughed, but I soon realised that one by one my brothers started to believe his nonsense. 'Solidarity and brotherly love' - Yeah right." +"darashia" -> "The wealth of Darashia is proverbial. I have my own little plans with it." +"scarab" -> "Some people find scarabs fascinating. Exactly why is simply beyond me." +"edron" -> "That is a human city, isn't it? I'd like to see it." +"thais" -> * +"venore" -> * +"kazordoon" -> "Isn't that a city built by dwarves?" +"carlin" -> * +"ab'dendriel" -> "I understand that city was built by elves." +"ankrahmun" -> "Ankrahmun is the oldest human settlement in Tibia. I remember how surprised we were when we found that humans were capable of building such fine cities." +"pharaoh" -> "They say that pharaoh established his own religion. Amazing. We djinn are notoriously given to pride, but no djinn would ever have the arrogance to proclaim himself god." +"palace" -> "They say that palace has turned into a home for the living dead under the new pharaoh's rule." +"ascension" -> "Hm. I don't know about this, human." +"kha'zeel" -> "Djinns have lived in these mountains, called Kha'zeel, for as long as anyone can remember. ...", + "Humans rarely come up here, and few djinn care to travel down to the Kha'labal. It is probably better this way. For both our races." +"kha'labal" -> "The Kha'labal we know today was created at the height of the djinn war. Not exactly a glorious chapter in the war's history." +"melchior" -> "Melchior tried to double-cross us. That worm! He paid his treachery dearly. No human messes with the Efreet and lives to tell the tale." +"alesar" -> "Ah yes - Alesar... Everybody here is really excited because of him. These fools seem to believe that winning the war will be a trifle now Malor is back and we have Alesar on our side. I am far more cautious. ...", + "I don't think Alesar is trying to double-cross us, but I have a bad feeling about him. I still don't understand what made him switch sides." +"fa'hradin" -> "I feel a strange kind of respect for Fa'hradin even though he is a Marid. He is more level-headed and clear-sighted than most Efreet I know. ...", + "I don't know what made him side with Gabel, but he is the only djinn I miss. ...", + "In fact he was one of the few djinn I ever liked." +"bo'ques" -> "Bo'ques. The fat cook. Exactly how he fits into the freaky ascetiscim of the Marid is beyond me." +"haroun" -> "Haroun. That name rings a bell. Isn't he Alesar brother? They used to be inseparable." +"baa'leal" -> "Baa'leal is our side's commander-in-chief. No wonder I can't be asked to join the service." +"lamp" -> "Yes - djinns sleep in lamps. How long did it take you to work that one out?" + +"wares" -> "My task is to buy and sell supplies. We are dealing with magical equipment like rings, amulets, rods and some special items." +"offer" -> * +"goods" -> * +"equipment" -> * +"do","you","sell" -> * +"do","you","have" -> * + +"amulets" -> "I'm selling and buying strange talismans, silver amulets, protection amulets and dragon necklaces." +"rings" -> "I'm selling and buying might rings, energy rings, life rings, time rings, dwarven rings and rings of healing." +"rods" -> "I'm buying snakebite rods, moonlight rods, volcanic rods, quagmire rods and tempest rods." +"wands" -> "I'm not interested in wands." +"special" -> "I'm currently looking for some special items. Do you have any ankhs or a mysterious fetish?" + +"might","ring" -> Type=3048, Amount=1, Price=5000, "Do you want to buy a might ring for %P gold?", Topic=10 +"energy","ring" -> Type=3051, Amount=1, Price=2000, "Do you want to buy an energy ring for %P gold?", Topic=10 +"life","ring" -> Type=3052, Amount=1, Price=900, "Do you want to buy a life ring for %P gold?", Topic=10 +"time","ring" -> Type=3053, Amount=1, Price=2000, "Do you want to buy a time ring for %P gold?", Topic=10 +"silver","amulet" -> Type=3054, Amount=1, Price=100, "Do you want to buy a silver amulet for %P gold?", Topic=10 +"strange","talisman" -> Type=3045, Amount=1, Price=100, "Do you want to buy a strange talisman for %P gold?", Topic=10 +"dwarven","ring" -> Type=3097, Amount=1, Price=2000, "Do you want to buy a dwarven ring for %P gold?", Topic=10 +"ring","of","healing" -> Type=3098, Amount=1, Price=2000, "Do you want to buy a ring of healing for %P gold?", Topic=10 +"protection","amulet" -> Type=3084, Amount=1, Price=700, "Do you want to buy a protection amulet for %P gold?", Topic=10 +"dragon","necklace" -> Type=3085, Amount=1, Price=1000, "Do you want to buy a dragon necklace for %P gold?", Topic=10 + +%1,1<%1,"strange","talisman" -> Type=3045, Amount=%1, Price=100*%1, "Do you want to buy %A strange talismans for %P gold?", Topic=10 +%1,1<%1,"might","ring" -> Type=3048, Amount=%1, Price=5000*%1, "Do you want to buy %A might rings for %P gold?", Topic=10 +%1,1<%1,"energy","ring" -> Type=3051, Amount=%1, Price=2000*%1, "Do you want to buy %A energy rings for %P gold?", Topic=10 +%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=900*%1, "Do you want to buy %A life rings for %P gold?", Topic=10 +%1,1<%1,"time","ring" -> Type=3053, Amount=%1, Price=2000*%1, "Do you want to buy %A time rings for %P gold?", Topic=10 +%1,1<%1,"silver","amulet" -> Type=3054, Amount=%1, Price=100*%1, "Do you want to buy %A silver amulets for %P gold?", Topic=10 +%1,1<%1,"dwarven","ring" -> Type=3097, Amount=%1, Price=2000*%1, "Do you want to buy %A dwarven rings for %P gold?", Topic=10 +%1,1<%1,"ring","of","healing" -> Type=3098, Amount=%1, Price=2000*%1, "Do you want to buy %A rings of healing for %P gold?", Topic=10 +%1,1<%1,"protection","amulet" -> Type=3084, Amount=%1, Price=700*%1, "Do you want to buy %A protection amulets for %P gold?", Topic=10 +%1,1<%1,"dragon","necklace" -> Type=3085, Amount=%1, Price=1000*%1, "Do you want to buy %A dragon necklaces for %P gold?", Topic=10 + +"sell","might","ring" -> Type=3048, Amount=1, Price=250, "Do you want to sell a might ring for %P gold?", Topic=11 +"sell","energy","ring" -> Type=3051, Amount=1, Price=100, "Do you want to sell an energy ring for %P gold?", Topic=11 +"sell","life","ring" -> Type=3052, Amount=1, Price=50, "Do you want to sell a life ring for %P gold?", Topic=11 +"sell","time","ring" -> Type=3053, Amount=1, Price=100, "Do you want to sell a time ring for %P gold?", Topic=11 +"sell","silver","amulet" -> Type=3054, Amount=1, Price=50, "Do you want to sell a silver amulet for %P gold?", Topic=11 +"sell","strange","talisman" -> Type=3045, Amount=1, Price=30, "Do you want to sell a strange talisman for %P gold?", Topic=11 +"sell","dwarven","ring" -> Type=3097, Amount=1, Price=100, "Do you want to sell a dwarven ring for %P gold?", Topic=11 +"sell","ring","of","healing" -> Type=3098, Amount=1, Price=100, "Do you want to sell a ring of healing for %P gold?", Topic=11 +"sell","protection","amulet" -> Type=3084, Amount=1, Price=100, "Do you want to sell a protection amulet for %P gold?", Topic=11 +"sell","dragon","necklace" -> Type=3085, Amount=1, Price=100, "Do you want to sell a dragon necklace for %P gold?", Topic=11 +"sell","mysterious","fetish" -> Type=3078, Amount=1, Price=50, "Do you want to sell a mysterious fetish for %P gold?", Topic=11 +"sell","ankh" -> Type=3077, Amount=1, Price=100, "Do you want to sell an ankh for %P gold?", Topic=11 + +"sell",%1,1<%1,"strange","talisman" -> Type=3045, Amount=%1, Price=30*%1, "Do you want to sell %A strange talismans for %P gold?", Topic=11 +"sell",%1,1<%1,"might","ring" -> Type=3048, Amount=%1, Price=250*%1, "Do you want to sell %A might rings for %P gold?", Topic=11 +"sell",%1,1<%1,"energy","ring" -> Type=3051, Amount=%1, Price=100*%1, "Do you want to sell %A energy rings for %P gold?", Topic=11 +"sell",%1,1<%1,"life","ring" -> Type=3052, Amount=%1, Price=50*%1, "Do you want to sell %A life rings for %P gold?", Topic=11 +"sell",%1,1<%1,"time","ring" -> Type=3053, Amount=%1, Price=100*%1, "Do you want to sell %A time rings for %P gold?", Topic=11 +"sell",%1,1<%1,"silver","amulet" -> Type=3054, Amount=%1, Price=50*%1, "Do you want to sell %A silver amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"dwarven","ring" -> Type=3097, Amount=%1, Price=100*%1, "Do you want to sell %A dwarven rings for %P gold?", Topic=11 +"sell",%1,1<%1,"ring","of","healing" -> Type=3098, Amount=%1, Price=100*%1, "Do you want to sell %A rings of healing for %P gold?", Topic=11 +"sell",%1,1<%1,"protection","amulet" -> Type=3084, Amount=%1, Price=100*%1, "Do you want to sell %A protection amulets for %P gold?", Topic=11 +"sell",%1,1<%1,"dragon","necklace" -> Type=3085, Amount=%1, Price=100*%1, "Do you want to sell %A dragon necklaces for %P gold?", Topic=11 +"sell",%1,1<%1,"mysterious","fetish" -> Type=3078, Amount=%1, Price=50*%1, "Do you want to sell %A mysterious fetishes for %P gold?", Topic=11 +"sell",%1,1<%1,"ankh" -> Type=3077, Amount=%1, Price=100*%1, "Do you want to sell %A ankhs for %P gold?", Topic=11 + +"sell","snakebite","rod" -> Type=3066, Amount=1, Price=100, "Do you want to sell a snakebite rod for %P gold?", Topic=11 +"sell","moonlight","rod" -> Type=3070, Amount=1, Price=200, "Do you want to sell a moonlight rod for %P gold?", Topic=11 +"sell","volcanic","rod" -> Type=3069, Amount=1, Price=1000, "Do you want to sell a volcanic rod for %P gold?", Topic=11 +"sell","quagmire","rod" -> Type=3065, Amount=1, Price=2000, "Do you want to sell a quagmire rod for %P gold?", Topic=11 +"sell","tempest","rod" -> Type=3067, Amount=1, Price=3000, "Do you want to sell a tempest rod for %P gold?", Topic=11 + +"sell",%1,1<%1,"snakebite","rod" -> Type=3066, Amount=%1, Price=100*%1, "Do you want to sell %A snakebite rods for %P gold?", Topic=11 +"sell",%1,1<%1,"moonlight","rod" -> Type=3070, Amount=%1, Price=200*%1, "Do you want to sell %A moonlight rods for %P gold?", Topic=11 +"sell",%1,1<%1,"volcanic","rod" -> Type=3069, Amount=%1, Price=1000*%1, "Do you want to sell %A volcanic rods for %P gold?", Topic=11 +"sell",%1,1<%1,"quagmire","rod" -> Type=3065, Amount=%1, Price=2000*%1, "Do you want to sell %A quagmire rods for %P gold?", Topic=11 +"sell",%1,1<%1,"tempest","rod" -> Type=3067, Amount=%1, Price=3000*%1, "Do you want to sell %A tempest rods for %P gold?", Topic=11 + +Topic=10,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=10,"yes",CountMoney>=Price -> "Good. Here you are.", DeleteMoney, Create(Type) +Topic=10,"yes" -> "You do not have enough gold, human!" +Topic=10 -> "As you wish." + +Topic=11,QuestValue(288)<3,! -> "I'm sorry, but you don't have Malor's permission to trade with me." +Topic=11,"yes",Count(Type)>=Amount -> "Good. Here is your money.", Delete(Type), CreateMoney +Topic=11,"yes" -> "You do not have one, human!" +Topic=11,"yes",Amount>1 -> "You do not have that many, human!" +Topic=11 -> "As you wish." +} diff --git a/data/npc/yanni.npc b/data/npc/yanni.npc new file mode 100644 index 0000000..08f3954 --- /dev/null +++ b/data/npc/yanni.npc @@ -0,0 +1,92 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yanni.npc: Datenbank für den Waffenhändler Yanni + +Name = "Yanni" +Outfit = (131,22-22-22-57) +Home = [32909,32111,6] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Salutations, %N. Welcome to the Ironhouse, warehouse of Abran Ironeye." +ADDRESS,"hi$",! -> * +ADDRESS,"hello$",! -> * +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "How rude..." + +"bye" -> "Thanks for doing business with us, traveller!", Idle +"farewell" -> * +"job" -> "I work for Abran Ironeye in the armor department." +"shop" -> "This shop belongs to Abran Ironeye." +"market" -> * +"ironhouse" -> * +"name" -> "My name is Yanni." +"time" -> "It is %T." +"Abran","Ironeye" -> "A very hard boss, but if you ever talk to him, tell him I said he's a great man." +"excalibug" -> "I don't believe in the excalibug myth." +"news" -> "I know nothing of interest." +"help" -> "If you'd like to help, then buy something, because I'm paid on a commisioned basis." +"commision" -> "Commison is when you get paid only when you sell something. If I don't make some sales soon, my kids will get hungry!" +"monster" -> "Monsters? Where! I'm horrified of monsters." +"thanks" -> "You're welcome." +"thank","you" -> * +"news" -> "Don't buy from Xed. He is a thief. He steals my business all the time!" +"offer" -> "I sell armor, legs, helmets, and shields." +"do","you","sell" -> * +"do","you","have" -> * +"helmet" -> "I am selling chain helmets. Do you want to buy any?" +"armor" -> "I am selling chain and brass armor. What do you need?" +"shield" -> "I am selling wooden shields and steel shields. What do you want?" +"trousers" -> "I am selling chain legs. Do you want to buy any?" +"legs" -> * + +"chain","armor" -> Type=3358, Amount=1, Price=200, "Do you want to buy a chain armor for %P gold?", Topic=1 +"brass","armor" -> Type=3359, Amount=1, Price=450, "Do you want to buy a brass armor for %P gold?", Topic=1 +"chain","helmet" -> Type=3352, Amount=1, Price=52, "Do you want to buy a chain helmet for %P gold?", Topic=1 +"steel","shield" -> Type=3409, Amount=1, Price=240, "Do you want to buy a steel shield for %P gold?", Topic=1 +"wooden","shield" -> Type=3412, Amount=1, Price=15, "Do you want to buy a wooden shield for %P gold?", Topic=1 +"chain","legs" -> Type=3558, Amount=1, Price=80, "Do you want to buy chain legs for %P gold?", Topic=1 + +%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=200*%1, "Do you want to buy %A chain armors for %P gold?", Topic=1 +%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=450*%1, "Do you want to buy %A brass armors for %P gold?", Topic=1 +%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=52*%1, "Do you want to buy %A chain helmets for %P gold?", Topic=1 +%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=240*%1, "Do you want to buy %A steel shields for %P gold?", Topic=1 +%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=15*%1, "Do you want to buy %A wooden shields for %P gold?", Topic=1 +%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=80*%1, "Do you want to buy %A chain legs for %P gold?", Topic=1 + +"sell","leather","armor" -> Type=3361, Amount=1, Price=12, "Do you want to sell a leather armor for %P gold?", Topic=2 +"sell","chain","armor" -> Type=3358, Amount=1, Price=70, "Do you want to sell a chain armor for %P gold?", Topic=2 +"sell","brass","armor" -> Type=3359, Amount=1, Price=150, "Do you want to sell a brass armor for %P gold?", Topic=2 +"sell","plate","armor" -> Type=3357, Amount=1, Price=400, "Do you want to sell a plate armor for %P gold?", Topic=2 +"sell","chain","legs" -> Type=3558, Amount=1, Price=25, "Do you want to sell chain legs for %P gold?", Topic=2 +"sell","leather","helmet" -> Type=3355, Amount=1, Price=4, "Do you want to sell a leather helmet for %P gold?", Topic=2 +"sell","chain","helmet" -> Type=3352, Amount=1, Price=17, "Do you want to sell a chain helmet for %P gold?", Topic=2 +"sell","steel","helmet" -> Type=3351, Amount=1, Price=190, "Do you want to sell a steel helmet for %P gold?", Topic=2 +"sell","wooden","shield" -> Type=3412, Amount=1, Price=5, "Do you want to sell a wooden shield for %P gold?", Topic=2 +"sell","battle","shield" -> Type=3413, Amount=1, Price=95, "Do you want to sell a battle shield for %P gold?", Topic=2 +"sell","steel","shield" -> Type=3409, Amount=1, Price=80, "Do you want to sell a steel shield for %P gold?", Topic=2 + +"sell",%1,1<%1,"leather","armor" -> Type=3361, Amount=%1, Price=12*%1, "Do you want to sell %A leather armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","armor" -> Type=3358, Amount=%1, Price=70*%1, "Do you want to sell %A chain armors for %P gold?", Topic=2 +"sell",%1,1<%1,"brass","armor" -> Type=3359, Amount=%1, Price=150*%1, "Do you want to sell %A brass armors for %P gold?", Topic=2 +"sell",%1,1<%1,"plate","armor" -> Type=3357, Amount=%1, Price=400*%1, "Do you want to sell %A plate armors for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","legs" -> Type=3558, Amount=%1, Price=25*%1, "Do you want to sell %A chain legs for %P gold?", Topic=2 +"sell",%1,1<%1,"leather","helmet" -> Type=3355, Amount=%1, Price=4*%1, "Do you want to sell %A leather helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"chain","helmet" -> Type=3352, Amount=%1, Price=17*%1, "Do you want to sell %A chain helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","helmet" -> Type=3351, Amount=%1, Price=190*%1, "Do you want to sell %A steel helmets for %P gold?", Topic=2 +"sell",%1,1<%1,"wooden","shield" -> Type=3412, Amount=%1, Price=5*%1, "Do you want to sell %A wooden shields for %P gold?", Topic=2 +"sell",%1,1<%1,"battle","shield" -> Type=3413, Amount=%1, Price=95*%1, "Do you want to sell %A battle shields for %P gold?", Topic=2 +"sell",%1,1<%1,"steel","shield" -> Type=3409, Amount=%1, Price=80*%1, "Do you want to sell %A steel shields for %P gold?", Topic=2 + +Topic=1,"yes",CountMoney>=Price -> "Thank you. Here it is.", DeleteMoney, Create(Type) +Topic=1,"yes" -> "Sorry, you do not have enough gold." +Topic=1 -> "Maybe you will buy it another time." + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so much." +Topic=2 -> "Maybe next time." +} diff --git a/data/npc/yberius.npc b/data/npc/yberius.npc new file mode 100644 index 0000000..5d80f7b --- /dev/null +++ b/data/npc/yberius.npc @@ -0,0 +1,101 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yberius.npc: Datenbank für den Mönch Yberius + +Name = "Yberius" +Outfit = (57,0-0-0-0) +Home = [32961,32076,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome, young %N! If you are new in Tibia, ask me for help." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Please wait a moment, %N.", Queue +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning, %N. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned, %N. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<40 -> "You are looking really bad, %N. Let me heal your wounds.", HP=40, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad, %N. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Remember: If you are heavily wounded or poisoned, I can heal you for free." + +"bye" -> "May the gods bless you, %N!", Idle +"farewell" -> * +"job" -> "I have no job. I just live for the gods of Tibia." +"name" -> "I am Brother Yberius." +"tibia" -> "It's the world of Tibia." +"god" -> "They are the creators of Tibia and all life on it." +"life" -> "There are the plants, the citizens, and the monsters." +"plant" -> "Crunor, the god of plants and fertility, watches over all plants, small and big." +"citizen" -> "Just walk around and meet them. Chat and learn about them." +"monster" -> "Even they have their part in the bigger scheme. Even if it eludes us mere mortals." +"king" -> "The king resides in the far away city of Thais." +"tibianus" -> * +"army" -> "The royal army is here to protect us." +"ferumbras" -> "The gods only know what this spawn of darkness might be up to." +"excalibug" -> "This blasphemous weapon has to be destroyed." +"news" -> "I won't take part in idle gossip." +"help" -> "Earn some gold and upgrade your equipment." +"quest" -> * +"task" -> * +"what","do" -> * +"gold" -> "If you need money, you have to slay monsters and take their gold. Look for swamptrolls in the swamps. But be careful. They use poisoned weapons." +"money" -> * +"equipment" -> "First you should buy a machete. You will need it in the swamps. And better don't explore without a shovel and a rope." +"eat" -> "If you want to heal your wounds, buy somthething to eat or hunt some small game. If you are very weak just ask me to heal you." +"food" -> * +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<40 -> "You are looking really bad. Let me heal your wounds.", HP=40, EffectOpp(13) + +"heal$",PvPEnforced -> "You aren't looking that bad." +"heal$" -> "You aren't looking that bad. Sorry, I can't help you. But if you are looking for additional protection you should go on the pilgrimage of ashes." + +"blessing",PvPEnforced,! -> "The lifeforce of this world is waning. There are no more blessings avaliable on this world." +"pilgrimage",PvPEnforced,! -> * +"ashes",PvPEnforced,! -> * + +"spiritual",PvPEnforced,! -> * +"shielding",PvPEnforced,! -> * +"sacred","places",PvPEnforced,! -> * +"spark",PvPEnforced,! -> * +"phoenix",PvPEnforced,! -> * +"embrace",PvPEnforced,! -> * +"fire",PvPEnforced,! -> * +"suns",PvPEnforced,! -> * +"wisdom",PvPEnforced,! -> * +"solitude",PvPEnforced,! -> * + + +"pilgrimage" -> "Whenever you receive a lethal wound your lifeforce is damaged. With every single of the five blessings you have this damage will be reduced." +"ashes" -> * + +"blessing" -> "There are five different blessings available in five sacred places. These blessings are: the spiritual shielding, the spark of the phoenix, the embrace of tibia, the fire of the suns and the wisdom of solitude." +"sacred","places" -> "Just ask in which of the five blessings you are interested in." + +"spiritual", QuestValue(104) > 0 -> "I see you received the spiritual shielding in the whiteflower temple south of Thais." +"shielding", QuestValue(104) > 0 -> * +"spiritual" -> "You can receive the spiritual shielding in the whiteflower temple south of Thais." +"shielding" -> * + +"spark", QuestValue(102) > 0 -> "I an sense that the spark of the phoenix already was given to you by the dwarven priests of earth and fire in Kazordoon." +"phoenix", QuestValue(102) > 0 -> * +"spark" -> "The spark of the phoenix is given by the dwarven priests of earth and fire in Kazordoon." +"phoenix" -> * + +"embrace", QuestValue(105) > 0 -> "I can sense the druids north of Carlin provided you with the embrace of tibia." +"embrace" -> "The druids north of Carlin can provide you with the embrace of tibia." + +"fire","suns", QuestValue(103) > 0 -> "I can see you recieved the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns", QuestValue(103) > 0 -> * +"fire","suns" -> "Ask for the blessing of the two suns in the suntower near Ab'Dendriel ." +"suns" -> * + + +"wisdom", QuestValue(101) > 0 -> "I can sense you already talked to the hermit Eremo on the isle of Cormaya and recieved this blessing." +"solitude", QuestValue(101) > 0 -> * +"wisdom" -> "Talk to the hermit Eremo on the isle of Cormaya about this blessing." +"solitude" -> * + +"time" -> "Now, it is %T. Ask Shiantis for a watch, if you need one." +} diff --git a/data/npc/yoem.npc b/data/npc/yoem.npc new file mode 100644 index 0000000..5802741 --- /dev/null +++ b/data/npc/yoem.npc @@ -0,0 +1,43 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yoem.npc: Möbelverkäufer Yoem auf Cormaya + +Name = "Yoem" +Outfit = (128,41-112-105-96) +Home = [33305,31966,7] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello %N! Do you need some equipment for your house?" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "One moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "My name is Yoem. I sell furniture and equipment." +"job" -> "Have you moved to a new home? I'm the specialist for equipping it." +"time" -> "It is %T. Do you need a clock for your house?" +"news" -> "You mean my specials, don't you?" + +"offer" -> "I sell statues, tables, chairs, flowers, pillows, pottery, instruments, decoration, tapestries and containers." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +@"gen-t-furniture-statues-s.ndb" +@"gen-t-furniture-tables-s.ndb" +@"gen-t-furniture-chairs-s.ndb" +@"gen-t-furniture-flowers-s.ndb" +@"gen-t-furniture-pillows-s.ndb" +@"gen-t-furniture-pottery-s.ndb" +@"gen-t-furniture-instruments-s.ndb" +@"gen-t-furniture-decoration-s.ndb" +@"gen-t-furniture-tapestries-s.ndb" +@"gen-t-furniture-containers-s.ndb" +} diff --git a/data/npc/yulas.npc b/data/npc/yulas.npc new file mode 100644 index 0000000..2ceb342 --- /dev/null +++ b/data/npc/yulas.npc @@ -0,0 +1,36 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# yulas.npc: Möbelverkäufer Yulas in Venore + +Name = "Yulas" +Outfit = (128,58-43-38-76) +Home = [33000,32065,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome to the Plank and Treasurechest Market, %N!" +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Just a moment please, %N.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Good bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"name" -> "I am Yulas. I will be your salesperson today." +"job" -> "We are into home improvement." +"time",male -> "It's %T, sire." +"time",female -> "It's %T, my lady." +"news" -> "Sorry, we are not allowed to chat." +"allen" -> "To think just because he is around here to watch what we do, he want to be considered one of us..." +"richardson" -> * + +"offer" -> "We sell furniture and equipment. At this counter you can buy tables." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * + +@"gen-t-furniture-tables-s.ndb" +} diff --git a/data/npc/zaidal.npc b/data/npc/zaidal.npc new file mode 100644 index 0000000..4746558 --- /dev/null +++ b/data/npc/zaidal.npc @@ -0,0 +1,67 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zaildal.npc: Datenbank für den Bambusmöbelhändler Zaidal + +Name = "Zaidal" +Outfit = (128,76-43-77-76) +Home = [32621,32747,5] +Radius = 0 + +Behaviour = { +ADDRESS,"hello$",! -> "Hello." +ADDRESS,"hi$",! -> * +ADDRESS,"hiho$",! -> * +ADDRESS,! -> Idle + +BUSY,"hello$",! -> "Please wait.", Queue +BUSY,"hi$",! -> * +BUSY,"hiho$",! -> * +BUSY,! -> NOP +VANISH,! -> "Bye." + +"bye" -> "Good bye.", Idle +"farewell" -> * +"job" -> "I am selling furniture, especially bamboo made furniture. I also buy elephant tusks to create my famous tusk tables and ivory chairs." +"name" -> "I am known as Zaidal." +"time" -> "Sorry, I have no idea." +"king" -> "Perhaps one day even the king will use our furniture." +"venore" -> "Well, Venore is the centre of commerce." +"thais" -> "One day I might visit Thais." +"carlin" -> "Carlin is very far to the north. I have never been there." +"edron" -> "An isle with interesting forests. With the right organisation the furniture and shipbuilding business could prosper enormously." +"jungle" -> "You call it a jungle, I call it furniture in the making." + +"offer" -> "I sell tables, chairs and drawers, all handmade with the material that the jungle has to offer." +"goods" -> * +"do","you","sell" -> * +"do","you","have" -> * +"furniture" -> * +"equipment" -> * +"special" -> "My offers are permanently extraordinarily cheap." + +"tibia" -> "The world is a big treasure chest for those who know to turn resources into profit." + +"kazordoon" -> "The dwarves of Kazordoon are experts in mining resources like gems and ore." +"dwarvs" -> * +"dwarfes" -> * +"ab'dendriel" -> "Those elves are a bit complicated when it comes to trees and environmental matters. They must learn that you have to crush an egg to bake a cake." +"elves" -> * +"elfs" -> * +"darama" -> "The jungle is rich in resources and who knows what profit lies hidden in the desert? Of course not for a carpenter, but there are other resources." +"darashia" -> "People there seem to know little about the world of economy. Perhaps someone might teach them a lesson one day." +"ankrahmun" -> "I was there only once, but I left it with the certainty that I never want to return there ever again." +"ferumbras" -> "If we could guide his attention in useful directions, he wouldn't be the problem he poses nowadays." +"excalibug" -> "Even if it would exist, which I doubt, it would be only an extremely expensive weapon and nothing more." +"apes" -> "They live in the depth of the jungle, and their only visits here are annoying raids to steal and plunder." +"lizard" -> "I did not see much of the lizzards yet." +"dworcs" -> "If we could get rid of them, a whole new area which is rich in bamboo would be ours." + +"sell","tusk" -> Type=3044, Amount=1, Price=100, "Do you want to sell a tusk for %P gold?", Topic=2 +"sell",%1,1<%1,"tusk" -> Type=3044, Amount=%1, Price=100*%1, "Do you want to sell %A tusks for %P gold?", Topic=2 + +Topic=2,"yes",Count(Type)>=Amount -> "Ok. Here is your money.", Delete(Type), CreateMoney +Topic=2,"yes" -> "Sorry, you do not have one." +Topic=2,"yes",Amount>1 -> "Sorry, you do not have so many." +Topic=2 -> "Maybe next time." + +@"gen-t-furniture-jungle-s.ndb" +} diff --git a/data/npc/zebron.npc b/data/npc/zebron.npc new file mode 100644 index 0000000..d92417b --- /dev/null +++ b/data/npc/zebron.npc @@ -0,0 +1,50 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zebron.npc: Datenbank für den Spieler Zebron + +Name = "Zebron" +Outfit = (128,95-15-109-76) +Home = [32931,32071,9] +Radius = 2 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings, high roller. So you care for a game, %N?", Topic=1 +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Take a minute to count your money, %N. I'll be here soon.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Hey, you can't leave. Luck is smiling on you. I can feel it!" + +"bye" -> "Hey, you can't leave. Luck is smiling on you. I can feel it!", Idle +"farewell" -> * +"job" -> "Oh, I am just sitting around here and gamble now and then." +"tavern" -> "It's a fine place to be around, isn't it?" +"name" -> "I am known as Zebron." +"time" -> "It is exactly %T." +"king" -> "Ah, our beloved king! Bless him for the gambling licence of Venore." +"tibianus" -> * +"licence" -> "I don't care much for that law stuff, but as far as I know those Venore merchants got a royal gambling licence for the city." +"venore" -> "Aaaah, Venore, Venore, what a wonderful town. Especially for someone with love for gambling like me." +"army" -> "Hehe, they spent a good part of their salary here in the tavern." +"excalibug" -> "I would not bet that anyone will ever find it." +"thais" -> "Thais is a bit too conservative for me." +"tibia" -> "What would I need more than that what I can get right here?" +"carlin" -> "Carlin, the beerless ... what a shame." +"hugo" -> "I had a cousin named hugo, why do you ask?" +"news" -> "Bah, always the same chitchat. Swampelves this and amazons that ... blah blah." +"rumors" -> * +"swamp" -> * +"amazon" -> * + +"gambl" -> "So you care for a civilized game of dice?", Topic=1 +"game" -> * +"dice" -> * + +Topic=1,"yes" -> "I will roll a dice. If it shows 6, you will get five times your bet. How much do you want to bet?", Amount=Random(1,6), Topic=2 +Topic=1,"no" -> "Oh come on, don't be a child." + +Topic=2,%1,0<%1,100>%1,CountMoney>=%1,Amount=6 -> Price=%1*5, "Ok, here we go ... 6! You have won %P, congratulations. One more game?", CreateMoney, Topic=1 +Topic=2,%1,0<%1,100>%1,CountMoney>=%1 -> Price=%1, "Ok, here we go ... %A! You have lost. Bad luck. One more game?", DeleteMoney, Topic=1 +Topic=2,%1,0<%1,100>%1 -> "I am sorry, but you don't have so much money. How much do you want to bet?", Topic=2 +Topic=2,%1 -> "I am sorry, but I accept only bets between 1 and 99 gold. I don't want to ruin you after all. How much do you want to bet?", Topic=2 +} diff --git a/data/npc/zerbrus.npc b/data/npc/zerbrus.npc new file mode 100644 index 0000000..4291ff5 --- /dev/null +++ b/data/npc/zerbrus.npc @@ -0,0 +1,51 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zerbrus.npc: Datenbank für den Dorfwächter Zerbrus (Rookgaard) + +Name = "Zerbrus" +Outfit = (131,76-38-76-95) +Home = [32022,32203,6] +Radius = 1 + +Behaviour = { +ADDRESS,"hello$",! -> "Greetings young traveller." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Not now." +BUSY,"hi$",! -> * +BUSY,"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +BUSY,"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +BUSY,"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +BUSY,"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +BUSY,! -> NOP +VANISH,! -> "Hm." + +"bye" -> "Bye.", Idle +"farewell" -> * +"how","are","you" -> "Fine." +"sell" -> "Ask the shopowners for their wares." +"advice",level<4 -> "Be careful out there and avoid the dungeons." +"advice",level>3 -> "Be careful out there." +"job" -> "I am the bridgeguard. I defend Rookgaard against the beasts of the wilderness and the dungeons!" +"name" -> "Zerbrus at your service." +"time" -> "My duty is eternal. Time is of no importance." +"help" -> "I have to stay here, sorry, but I can heal you if you are wounded." +"monster" -> "I will slay all monsters who dare to attack this little town." +"dungeon" -> "Dungeons are dangerous, be prepared." +"wilderness" -> "There are wolves, bears, snakes, deers, and spiders. You can find some dungeon entrances there, too." +"sewer" -> "In the sewers are crowded with rats. They make fine targets for young heroes." +"god" -> "I am a follower of Banor." +"dallheim" -> "He does a fine job." +"banor" -> "The heavenly warrior! Read books to learn about him." +"king" -> "HAIL TO THE KING!" +"seymour" -> "His job to teach the young heroes is important for our all survival." +"willie" -> "He can swear and curse as good as the rowdyest seaman I met." +"amber" -> "Shes verry attractive. To bad my duty leaves me no time to date her." +"hyacinth" -> "One of theese reclusive druids." +"weapon" -> "My weapon is property of the royal army. Find your own one." +"magic" -> "You will learn about magic soon enough." +"tibia" -> "In the world of tibia many challenges await the brave adventurers." +"heal$",Burning>0 -> "You are burning. I will help you.", Burning(0,0), EffectOpp(15) +"heal$",Poison>0 -> "You are poisoned. I will help you.", Poison(0,0), EffectOpp(14) +"heal$",HP<65 -> "You are looking really bad. Let me heal your wounds.", HP=65, EffectOpp(13) +"heal$" -> "You aren't looking really bad. Sorry, I can't help you." +} diff --git a/data/npc/zoltan.npc b/data/npc/zoltan.npc new file mode 100644 index 0000000..7de24a2 --- /dev/null +++ b/data/npc/zoltan.npc @@ -0,0 +1,62 @@ +# GIMUD - Graphical Interface Multi User Dungeon +# zoltan.npc Datenbank fuer den Zauberlehrer Zoltan + +Name = "Zoltan" +Outfit = (130,95-94-95-57) +Home = [33268,31849,4] +Radius = 3 + +Behaviour = { +ADDRESS,"hello$",! -> "Welcome %N, student of the arcane arts." +ADDRESS,"hi$",! -> * +ADDRESS,! -> Idle +BUSY,"hello$",! -> "Wait %N. Your time will come.", Queue +BUSY,"hi$",! -> * +BUSY,! -> NOP +VANISH,! -> "Use your knowledge wisely." + +"bye" -> "Use your knowledge wisely", Idle +"job" -> "I am a teacher of the most powerful spells in Tibia." +"name" -> "I am known in this world as Zoltan." +"time" -> "It's %T." +"king" -> "King Tibianus III was the founder of our academy." +"tibianus" -> * +"army" -> "They rely too much on their brawn instead of their brain." +"ferumbras" -> "A fallen sorcerer, indeed. What a shame." +"excalibug" -> "You will need no weapon if you manipulate the essence of magic." +"thais" -> "Thais is a place of barbary." +"tibia" -> "There is still much left to be explored in this world." +"carlin" -> "Carlin's druids waste the influence they have in enviromentalism." +"edron" -> "Sciences are thriving on this isle." +"news" -> "I have no time for chit chat." +"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." + +"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." +"crunor","caress",QuestValue(211)=1 -> "A quite undruidic order of druids they were, as far as we know. I have no more enlightening knowledge about them though.",SetQuestValue(211,2) +"crunor","caress",QuestValue(211)>1 -> * +"crunor","caress",QuestValue(211)=0 -> "I am quite busy, ask another time!" + + +"spellbook" -> "Don't bother me with that. Ask in the shops for it." +"spell" -> "I have some very powerful spells: 'Energy Bomb', 'Mass Healing', 'Poison Storm', 'Paralyze', and 'Ultimate Explosion'." + +"energy","bomb",Sorcerer -> String="Energybomb", Price=2300, "Are you prepared to learn the spell 'Energy Bomb' for %P gold?", Topic=1 +"energy","bomb" -> "No, no, no. This dangerous spell is only for sorcerers." +"mass","healing",Druid -> String="Mass Healing", Price=2200, "Are you prepared to learn the spell 'Mass Healing' for %P gold?", Topic=1 +"mass","healing" -> "No, no, no. This elemental spell is only for druids." +"poison","storm",Druid -> String="Poison Storm", Price=3400, "Are you prepared to learn the spell 'Poison Storm' for %P gold?", Topic=1 +"poison","storm" -> "No, no, no. This elemental spell is only for druids." +"paralyze",Druid -> String="Paralyze", Price=1900, "Are you prepared to learn the spell 'Paralyze' for %P gold?", Topic=1 +"paralyze" -> "No, no, no. This elemental spell is only for druids." +"ultimate","explosion",Sorcerer -> String="Ultimate Explosion", Price=8000, "Are you prepared to learn the spell 'Ultimate Explosion' for %P gold?", Topic=1 +"ultimate","explosion" -> "No, no, no. This dangerous spell is only for sorcerers." + +Topic=1,"yes",SpellKnown(String)=1 -> "Want to fool me? You already know this spell." +Topic=1,"yes",Level Amount=SpellLevel(String), "Don't be in high spirits. Advance to level %A, and then come again." +Topic=1,"yes",CountMoney "Want to learn one of the most powerful spells, and don't even know how much money you have?" +Topic=1,"yes" -> "Congratulations. You now know one of the most powerful spells. Use it wisely.", DeleteMoney, EffectOpp(13), TeachSpell(String) +Topic=1 -> "Lost your heart?" +} diff --git a/data/raids/abdendrielbadgers.xml b/data/raids/abdendrielbadgers.xml new file mode 100644 index 0000000..0e4a428 --- /dev/null +++ b/data/raids/abdendrielbadgers.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/abdendrielwolfattack.xml b/data/raids/abdendrielwolfattack.xml new file mode 100644 index 0000000..5c62242 --- /dev/null +++ b/data/raids/abdendrielwolfattack.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/ankrahmunscarabinvasion.xml b/data/raids/ankrahmunscarabinvasion.xml new file mode 100644 index 0000000..d8b2aea --- /dev/null +++ b/data/raids/ankrahmunscarabinvasion.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/carlintowerorcs.xml b/data/raids/carlintowerorcs.xml new file mode 100644 index 0000000..ed92619 --- /dev/null +++ b/data/raids/carlintowerorcs.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/cavesgrorlam0.xml b/data/raids/cavesgrorlam0.xml new file mode 100644 index 0000000..6639a7c --- /dev/null +++ b/data/raids/cavesgrorlam0.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cavesgrorlam1.xml b/data/raids/cavesgrorlam1.xml new file mode 100644 index 0000000..035da83 --- /dev/null +++ b/data/raids/cavesgrorlam1.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cavesgrorlam2.xml b/data/raids/cavesgrorlam2.xml new file mode 100644 index 0000000..0923366 --- /dev/null +++ b/data/raids/cavesgrorlam2.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cavesgrorlam3.xml b/data/raids/cavesgrorlam3.xml new file mode 100644 index 0000000..eb50904 --- /dev/null +++ b/data/raids/cavesgrorlam3.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cavesgrorlam4.xml b/data/raids/cavesgrorlam4.xml new file mode 100644 index 0000000..c40383e --- /dev/null +++ b/data/raids/cavesgrorlam4.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cavesgrorlam5.xml b/data/raids/cavesgrorlam5.xml new file mode 100644 index 0000000..4987d83 --- /dev/null +++ b/data/raids/cavesgrorlam5.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/cormayadwarfattack.xml b/data/raids/cormayadwarfattack.xml new file mode 100644 index 0000000..6314aa0 --- /dev/null +++ b/data/raids/cormayadwarfattack.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/raids/darashiaundeadinvasion.xml b/data/raids/darashiaundeadinvasion.xml new file mode 100644 index 0000000..4bdf4c4 --- /dev/null +++ b/data/raids/darashiaundeadinvasion.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/darashiawaspplague.xml b/data/raids/darashiawaspplague.xml new file mode 100644 index 0000000..24e279d --- /dev/null +++ b/data/raids/darashiawaspplague.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/data/raids/dracoriadieingdragons.xml b/data/raids/dracoriadieingdragons.xml new file mode 100644 index 0000000..4c609b3 --- /dev/null +++ b/data/raids/dracoriadieingdragons.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/data/raids/drefianecromancer.xml b/data/raids/drefianecromancer.xml new file mode 100644 index 0000000..3417e56 --- /dev/null +++ b/data/raids/drefianecromancer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/raids/edronorshabaal.xml b/data/raids/edronorshabaal.xml new file mode 100644 index 0000000..6c3250c --- /dev/null +++ b/data/raids/edronorshabaal.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/edronskunks.xml b/data/raids/edronskunks.xml new file mode 100644 index 0000000..5215071 --- /dev/null +++ b/data/raids/edronskunks.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/foldayetis.xml b/data/raids/foldayetis.xml new file mode 100644 index 0000000..4ee0726 --- /dev/null +++ b/data/raids/foldayetis.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/halloweenhare.xml b/data/raids/halloweenhare.xml new file mode 100644 index 0000000..20a8b2f --- /dev/null +++ b/data/raids/halloweenhare.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/raids/kazordoonhornedfox.xml b/data/raids/kazordoonhornedfox.xml new file mode 100644 index 0000000..c244a9d --- /dev/null +++ b/data/raids/kazordoonhornedfox.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/data/raids/kazordoonspiderplague.xml b/data/raids/kazordoonspiderplague.xml new file mode 100644 index 0000000..d50decb --- /dev/null +++ b/data/raids/kazordoonspiderplague.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/mintwalinminogeneral.xml b/data/raids/mintwalinminogeneral.xml new file mode 100644 index 0000000..ccf5d0f --- /dev/null +++ b/data/raids/mintwalinminogeneral.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/data/raids/mistisledruid.xml b/data/raids/mistisledruid.xml new file mode 100644 index 0000000..99ec53d --- /dev/null +++ b/data/raids/mistisledruid.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/data/raids/necropolisbeholder.xml b/data/raids/necropolisbeholder.xml new file mode 100644 index 0000000..93ca69d --- /dev/null +++ b/data/raids/necropolisbeholder.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/northroadoutlaws.xml b/data/raids/northroadoutlaws.xml new file mode 100644 index 0000000..b330e3c --- /dev/null +++ b/data/raids/northroadoutlaws.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/data/raids/orclandorc.xml b/data/raids/orclandorc.xml new file mode 100644 index 0000000..2d661de --- /dev/null +++ b/data/raids/orclandorc.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/data/raids/pohdemodras.xml b/data/raids/pohdemodras.xml new file mode 100644 index 0000000..e8addab --- /dev/null +++ b/data/raids/pohdemodras.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/raids/pohwidow.xml b/data/raids/pohwidow.xml new file mode 100644 index 0000000..e86fc7c --- /dev/null +++ b/data/raids/pohwidow.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/data/raids/raids.xml b/data/raids/raids.xml new file mode 100644 index 0000000..a19c02d --- /dev/null +++ b/data/raids/raids.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/rookgaardrats.xml b/data/raids/rookgaardrats.xml new file mode 100644 index 0000000..1a15db6 --- /dev/null +++ b/data/raids/rookgaardrats.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/shadowthorndharalion.xml b/data/raids/shadowthorndharalion.xml new file mode 100644 index 0000000..72ee27b --- /dev/null +++ b/data/raids/shadowthorndharalion.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/data/raids/stonehomeghoulattack.xml b/data/raids/stonehomeghoulattack.xml new file mode 100644 index 0000000..5e28fe5 --- /dev/null +++ b/data/raids/stonehomeghoulattack.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/data/raids/thaiscaverats.xml b/data/raids/thaiscaverats.xml new file mode 100644 index 0000000..7311310 --- /dev/null +++ b/data/raids/thaiscaverats.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/thaislighthouseorcs.xml b/data/raids/thaislighthouseorcs.xml new file mode 100644 index 0000000..e14907c --- /dev/null +++ b/data/raids/thaislighthouseorcs.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/thaisorcinvasion.xml b/data/raids/thaisorcinvasion.xml new file mode 100644 index 0000000..52e2066 --- /dev/null +++ b/data/raids/thaisorcinvasion.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/venoreelfinvasion.xml b/data/raids/venoreelfinvasion.xml new file mode 100644 index 0000000..79ac206 --- /dev/null +++ b/data/raids/venoreelfinvasion.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/raids/venoreswampelves.xml b/data/raids/venoreswampelves.xml new file mode 100644 index 0000000..7aa3ec8 --- /dev/null +++ b/data/raids/venoreswampelves.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/data/spells/lib/spells.lua b/data/spells/lib/spells.lua new file mode 100644 index 0000000..b66dc13 --- /dev/null +++ b/data/spells/lib/spells.lua @@ -0,0 +1,220 @@ +function healingFormula(level, maglevel, base, variation) + local value = 2 * level + (3 * maglevel) + local min = value - math.random(variation) + base / 100 + local max = value + math.random(variation) + base / 100 + return min, max +end + +function damageFormula(level, maglevel, base, variation) + local value = 2 * level + (3 * maglevel) + local min = value - math.random(variation) + base / 100 + local max = value + math.random(variation) + base / 100 + return -min, -max +end + +function computeFormula(level, maglevel, base, variation) + local damage = base + if variation > 0 then + damage = math.random(-variation, variation) + damage + end + + local level_formula = 2 * level + local magic_formula = 3 * maglevel + level_formula + + return magic_formula * damage / 100 +end + +--------------------------------------------------------------------------------------- + +AREA_WAVE3 = { +{1, 1, 1}, +{1, 1, 1}, +{0, 3, 0} +} + +AREA_WAVE4 = { +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0}, +{0, 1, 1, 1, 0}, +{0, 0, 3, 0, 0} +} + +AREA_WAVE6 = { +{0, 0, 0, 0, 0}, +{0, 1, 3, 1, 0}, +{0, 0, 0, 0, 0} +} + +AREA_SQUAREWAVE5 = { +{1, 1, 1}, +{1, 1, 1}, +{1, 1, 1}, +{0, 1, 0}, +{0, 3, 0} +} + +AREA_SQUAREWAVE6 = { +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0} +} + +AREA_SQUAREWAVE7 = { +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0} +} + +--Diagonal waves +AREADIAGONAL_WAVE4 = { +{0, 0, 0, 0, 1, 0}, +{0, 0, 0, 1, 1, 0}, +{0, 0, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 0}, +{0, 0, 0, 0, 0, 3} +} + +AREADIAGONAL_SQUAREWAVE5 = { +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{1, 1, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +AREADIAGONAL_WAVE6 = { +{0, 0, 1}, +{0, 3, 0}, +{1, 0, 0} +} + +--Beams +AREA_BEAM1 = { +{3} +} + +AREA_BEAM5 = { +{1}, +{1}, +{1}, +{1}, +{3} +} + +AREA_BEAM7 = { +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{3} +} + +AREA_BEAM8 = { +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{1}, +{3} +} + +--Diagonal Beams +AREADIAGONAL_BEAM5 = { +{1, 0, 0, 0, 0}, +{0, 1, 0, 0, 0}, +{0, 0, 1, 0, 0}, +{0, 0, 0, 1, 0}, +{0, 0, 0, 0, 3} +} + +AREADIAGONAL_BEAM7 = { +{1, 0, 0, 0, 0, 0, 0}, +{0, 1, 0, 0, 0, 0, 0}, +{0, 0, 1, 0, 0, 0, 0}, +{0, 0, 0, 1, 0, 0, 0}, +{0, 0, 0, 0, 1, 0, 0}, +{0, 0, 0, 0, 0, 1, 0}, +{0, 0, 0, 0, 0, 0, 3} +} + +--Circles +AREA_CIRCLE2X2 = { +{0, 1, 1, 1, 0}, +{1, 1, 1, 1, 1}, +{1, 1, 3, 1, 1}, +{1, 1, 1, 1, 1}, +{0, 1, 1, 1, 0} +} + +AREA_CIRCLE3X3 = { +{0, 0, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 1, 1}, +{1, 1, 1, 3, 1, 1, 1}, +{1, 1, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 0, 0} +} + +-- Crosses +AREA_CROSS1X1 = { +{0, 1, 0}, +{1, 3, 1}, +{0, 1, 0} +} + +AREA_CIRCLE5X5 = { +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, +{0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0}, +{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0}, +{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0} +} + +--Squares +AREA_SQUARE1X1 = { +{1, 1, 1}, +{1, 3, 1}, +{1, 1, 1} +} + +-- Walls +AREA_WALLFIELD = { +{1, 1, 3, 1, 1} +} + +AREADIAGONAL_WALLFIELD = { +{0, 0, 0, 0, 1}, +{0, 0, 0, 1, 1}, +{0, 1, 3, 1, 0}, +{1, 1, 0, 0, 0}, +{1, 0, 0, 0, 0}, +} \ No newline at end of file diff --git a/data/spells/scripts/runes/animate dead.lua b/data/spells/scripts/runes/animate dead.lua new file mode 100644 index 0000000..c5e55c6 --- /dev/null +++ b/data/spells/scripts/runes/animate dead.lua @@ -0,0 +1,29 @@ +local humanBodies = { + 4240, 4241, 4247, 4248 +} + +function onCastSpell(creature, variant) + local position = Variant.getPosition(variant) + local tile = Tile(position) + if tile then + local corpse = tile:getTopDownItem() + if corpse then + local itemType = corpse:getType() + if not table.contains(humanBodies, itemType:getId()) then + if itemType:isCorpse() and itemType:isMovable() then + local monster = Game.createMonster("Skeleton", position) + if monster then + corpse:remove() + monster:setMaster(creature) + position:sendMagicEffect(CONST_ME_MAGIC_BLUE) + return true + end + end + end + end + end + + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false +end \ No newline at end of file diff --git a/data/spells/scripts/runes/cure poison.lua b/data/spells/scripts/runes/cure poison.lua new file mode 100644 index 0000000..b248b41 --- /dev/null +++ b/data/spells/scripts/runes/cure poison.lua @@ -0,0 +1,8 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_POISON) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/destroy field.lua b/data/spells/scripts/runes/destroy field.lua new file mode 100644 index 0000000..076cdc3 --- /dev/null +++ b/data/spells/scripts/runes/destroy field.lua @@ -0,0 +1,29 @@ +local fieldIds = { + 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, + 2126, 2127, 2131, 2132, 2133, 2134, 2135 +} + +function onCastSpell(creature, variant, isHotkey) + local position = Variant.getPosition(variant) + local tile = Tile(position) + local field = tile and tile:getItemByType(ITEM_TYPE_MAGICFIELD) + + if field and table.contains(fieldIds, field:getId()) then + field:remove() + position:sendMagicEffect(CONST_ME_POFF) + return true + end + + for _, id in ipairs(fieldIds) do + field = tile and tile:getItemById(id) + if field then + field:remove() + position:sendMagicEffect(CONST_ME_POFF) + return true + end + end + + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + return false +end \ No newline at end of file diff --git a/data/spells/scripts/runes/disintegrate.lua b/data/spells/scripts/runes/disintegrate.lua new file mode 100644 index 0000000..90a92dd --- /dev/null +++ b/data/spells/scripts/runes/disintegrate.lua @@ -0,0 +1,26 @@ +local dead_human = { + 4240, 4241, 4242, 4247, 4248 +} +local removalLimit = 10 + +function onCastSpell(creature, variant) + local position = variant:getPosition() + local tile = Tile(position) + if tile then + local items = tile:getItems() + if items then + for i, item in ipairs(items) do + if item:getType():isMovable() and item:getActionId() == 0 and not table.contains(dead_human, item:getId()) then + item:remove() + end + + if i == removalLimit then + break + end + end + end + end + + position:sendMagicEffect(CONST_ME_POFF) + return true +end \ No newline at end of file diff --git a/data/spells/scripts/runes/energy field.lua b/data/spells/scripts/runes/energy field.lua new file mode 100644 index 0000000..84dbafb --- /dev/null +++ b/data/spells/scripts/runes/energy field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/energy wall.lua b/data/spells/scripts/runes/energy wall.lua new file mode 100644 index 0000000..ed33b55 --- /dev/null +++ b/data/spells/scripts/runes/energy wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/energybomb.lua b/data/spells/scripts/runes/energybomb.lua new file mode 100644 index 0000000..77ef1ec --- /dev/null +++ b/data/spells/scripts/runes/energybomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_ENERGYHIT) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2122) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/envenom.lua b/data/spells/scripts/runes/envenom.lua new file mode 100644 index 0000000..5947017 --- /dev/null +++ b/data/spells/scripts/runes/envenom.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYPOISON) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_POISON) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 70, 20)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/explosion.lua b/data/spells/scripts/runes/explosion.lua new file mode 100644 index 0000000..84b113e --- /dev/null +++ b/data/spells/scripts/runes/explosion.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) +combat:setArea(createCombatArea(AREA_CROSS1X1)) + +function onGetFormulaValues(player, level, maglevel) + local base = 60 + local variation = 40 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/fire field.lua b/data/spells/scripts/runes/fire field.lua new file mode 100644 index 0000000..4743641 --- /dev/null +++ b/data/spells/scripts/runes/fire field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/fire wall.lua b/data/spells/scripts/runes/fire wall.lua new file mode 100644 index 0000000..f75ab13 --- /dev/null +++ b/data/spells/scripts/runes/fire wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/fireball.lua b/data/spells/scripts/runes/fireball.lua new file mode 100644 index 0000000..a5a32fe --- /dev/null +++ b/data/spells/scripts/runes/fireball.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +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 formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/firebomb.lua b/data/spells/scripts/runes/firebomb.lua new file mode 100644 index 0000000..90ca33f --- /dev/null +++ b/data/spells/scripts/runes/firebomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setParameter(COMBAT_PARAM_MAGICEFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2118) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/great fireball.lua b/data/spells/scripts/runes/great fireball.lua new file mode 100644 index 0000000..3da1f52 --- /dev/null +++ b/data/spells/scripts/runes/great fireball.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +function onGetFormulaValues(player, level, maglevel) + local base = 50 + local variation = 15 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/heavy magic missile.lua b/data/spells/scripts/runes/heavy magic missile.lua new file mode 100644 index 0000000..9af1280 --- /dev/null +++ b/data/spells/scripts/runes/heavy magic missile.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) + +function onGetFormulaValues(player, level, maglevel) + local base = 30 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/intense healing.lua b/data/spells/scripts/runes/intense healing.lua new file mode 100644 index 0000000..c93a2ea --- /dev/null +++ b/data/spells/scripts/runes/intense healing.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 70 + local variation = 30 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/light magic missile.lua b/data/spells/scripts/runes/light magic missile.lua new file mode 100644 index 0000000..3a83c2f --- /dev/null +++ b/data/spells/scripts/runes/light magic missile.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) + +function onGetFormulaValues(player, level, maglevel) + local base = 15 + local variation = 5 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/magic wall.lua b/data/spells/scripts/runes/magic wall.lua new file mode 100644 index 0000000..25a7c05 --- /dev/null +++ b/data/spells/scripts/runes/magic wall.lua @@ -0,0 +1,7 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2128) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/paralyze.lua b/data/spells/scripts/runes/paralyze.lua new file mode 100644 index 0000000..9248c01 --- /dev/null +++ b/data/spells/scripts/runes/paralyze.lua @@ -0,0 +1,16 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) + +local condition = Condition(CONDITION_PARALYZE) +condition:setParameter(CONDITION_PARAM_TICKS, 10000) +condition:setSpeedDelta(-101) +combat:setCondition(condition) + +function onCastSpell(creature, variant, isHotkey) + if not combat:execute(creature, variant) then + return false + end + + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true +end \ No newline at end of file diff --git a/data/spells/scripts/runes/poison bomb.lua b/data/spells/scripts/runes/poison bomb.lua new file mode 100644 index 0000000..4e75d3b --- /dev/null +++ b/data/spells/scripts/runes/poison bomb.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/poison field.lua b/data/spells/scripts/runes/poison field.lua new file mode 100644 index 0000000..c895b87 --- /dev/null +++ b/data/spells/scripts/runes/poison field.lua @@ -0,0 +1,9 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/poison wall.lua b/data/spells/scripts/runes/poison wall.lua new file mode 100644 index 0000000..4669236 --- /dev/null +++ b/data/spells/scripts/runes/poison wall.lua @@ -0,0 +1,10 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_POISON) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2121) +combat:setArea(createCombatArea(AREA_WALLFIELD, AREADIAGONAL_WALLFIELD)) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/soulfire.lua b/data/spells/scripts/runes/soulfire.lua new file mode 100644 index 0000000..33ad3e6 --- /dev/null +++ b/data/spells/scripts/runes/soulfire.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_FIRE) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 120, 20)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/sudden death.lua b/data/spells/scripts/runes/sudden death.lua new file mode 100644 index 0000000..4911c5d --- /dev/null +++ b/data/spells/scripts/runes/sudden death.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_DEATH) + +function onGetFormulaValues(player, level, maglevel) + local base = 150 + local variation = 20 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/ultimate healing.lua b/data/spells/scripts/runes/ultimate healing.lua new file mode 100644 index 0000000..32317e0 --- /dev/null +++ b/data/spells/scripts/runes/ultimate healing.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_TARGETCASTERORTOPMOST, true) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 250 + local variation = 0 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/runes/wild growth.lua b/data/spells/scripts/runes/wild growth.lua new file mode 100644 index 0000000..c865b0d --- /dev/null +++ b/data/spells/scripts/runes/wild growth.lua @@ -0,0 +1,7 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY) +combat:setParameter(COMBAT_PARAM_CREATEITEM, 2130) + +function onCastSpell(creature, variant, isHotkey) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/antidote.lua b/data/spells/scripts/spells/antidote.lua new file mode 100644 index 0000000..b248b41 --- /dev/null +++ b/data/spells/scripts/spells/antidote.lua @@ -0,0 +1,8 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_POISON) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/berserk.lua b/data/spells/scripts/spells/berserk.lua new file mode 100644 index 0000000..ebb0aec --- /dev/null +++ b/data/spells/scripts/spells/berserk.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITAREA) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, true) +combat:setParameter(COMBAT_PARAM_BLOCKSHIELD, false) +combat:setParameter(COMBAT_PARAM_USECHARGES, true) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onGetFormulaValues(player, skill, attack, fightMode) + local base = 80 + local variation = 20 + local formula = 3 * player:getMagicLevel() + (2 * player:getLevel()) + local damage = formula * base / 100 + damage = damage * attack / 25 + return -damage - variation, -damage + variation +end + +combat:setCallback(CALLBACK_PARAM_SKILLVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/cancel invisibility.lua b/data/spells/scripts/spells/cancel invisibility.lua new file mode 100644 index 0000000..4daab2d --- /dev/null +++ b/data/spells/scripts/spells/cancel invisibility.lua @@ -0,0 +1,8 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_INVISIBLE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/challenge.lua b/data/spells/scripts/spells/challenge.lua new file mode 100644 index 0000000..4cdc09e --- /dev/null +++ b/data/spells/scripts/spells/challenge.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setArea(createCombatArea(AREA_SQUARE1X1)) + +function onTargetCreature(creature, target) + return doChallengeCreature(creature, target) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/energy beam.lua b/data/spells/scripts/spells/energy beam.lua new file mode 100644 index 0000000..2858301 --- /dev/null +++ b/data/spells/scripts/spells/energy beam.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONHIT) +combat:setArea(createCombatArea(AREA_BEAM5, AREADIAGONAL_BEAM5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 60 + local variation = 20 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/energy strike.lua b/data/spells/scripts/spells/energy strike.lua new file mode 100644 index 0000000..91a9d90 --- /dev/null +++ b/data/spells/scripts/spells/energy strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_TELEPORT) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/energy wave.lua b/data/spells/scripts/spells/energy wave.lua new file mode 100644 index 0000000..158850a --- /dev/null +++ b/data/spells/scripts/spells/energy wave.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_TELEPORT) +combat:setArea(createCombatArea(AREA_SQUAREWAVE5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 150 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/fire wave.lua b/data/spells/scripts/spells/fire wave.lua new file mode 100644 index 0000000..904aeee --- /dev/null +++ b/data/spells/scripts/spells/fire wave.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE) +combat:setArea(createCombatArea(AREA_WAVE4, AREADIAGONAL_WAVE4)) + +function onGetFormulaValues(player, level, maglevel) + local base = 30 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/flame strike.lua b/data/spells/scripts/spells/flame strike.lua new file mode 100644 index 0000000..cd9eb53 --- /dev/null +++ b/data/spells/scripts/spells/flame strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/food.lua b/data/spells/scripts/spells/food.lua new file mode 100644 index 0000000..bf185ec --- /dev/null +++ b/data/spells/scripts/spells/food.lua @@ -0,0 +1,9 @@ +local food = { + 3577, 3582, 3585, 3592, 3602 +} + +function onCastSpell(creature, variant) + creature:addItem(food[math.random(#food)]) + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true +end \ No newline at end of file diff --git a/data/spells/scripts/spells/force strike.lua b/data/spells/scripts/spells/force strike.lua new file mode 100644 index 0000000..e693a52 --- /dev/null +++ b/data/spells/scripts/spells/force strike.lua @@ -0,0 +1,20 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA) + +function onGetFormulaValues(player, level, maglevel) + local base = 45 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/great energy beam.lua b/data/spells/scripts/spells/great energy beam.lua new file mode 100644 index 0000000..a5de443 --- /dev/null +++ b/data/spells/scripts/spells/great energy beam.lua @@ -0,0 +1,21 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) +combat:setArea(createCombatArea(AREA_BEAM8)) + +function onGetFormulaValues(player, level, maglevel) + local base = 120 + local variation = 80 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/great light.lua b/data/spells/scripts/spells/great light.lua new file mode 100644 index 0000000..25e8be0 --- /dev/null +++ b/data/spells/scripts/spells/great light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 8) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (11 * 60 + 35) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/haste.lua b/data/spells/scripts/spells/haste.lua new file mode 100644 index 0000000..ac6cdf5 --- /dev/null +++ b/data/spells/scripts/spells/haste.lua @@ -0,0 +1,12 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_HASTE) +condition:setParameter(CONDITION_PARAM_TICKS, 30000) +condition:setSpeedDelta(30) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/heal friend.lua b/data/spells/scripts/spells/heal friend.lua new file mode 100644 index 0000000..f3fd7d9 --- /dev/null +++ b/data/spells/scripts/spells/heal friend.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 120 + local variation = 40 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + creature:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/intense healing.lua b/data/spells/scripts/spells/intense healing.lua new file mode 100644 index 0000000..6ae3f67 --- /dev/null +++ b/data/spells/scripts/spells/intense healing.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 40 + local variation = 20 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/invisible.lua b/data/spells/scripts/spells/invisible.lua new file mode 100644 index 0000000..f538e45 --- /dev/null +++ b/data/spells/scripts/spells/invisible.lua @@ -0,0 +1,11 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_INVISIBLE) +condition:setParameter(CONDITION_PARAM_TICKS, 200000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/light healing.lua b/data/spells/scripts/spells/light healing.lua new file mode 100644 index 0000000..4f9eeb4 --- /dev/null +++ b/data/spells/scripts/spells/light healing.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 20 + local variation = 10 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/light.lua b/data/spells/scripts/spells/light.lua new file mode 100644 index 0000000..dd9e624 --- /dev/null +++ b/data/spells/scripts/spells/light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 6) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (6 * 60 + 10) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/magic rope.lua b/data/spells/scripts/spells/magic rope.lua new file mode 100644 index 0000000..0efdd4e --- /dev/null +++ b/data/spells/scripts/spells/magic rope.lua @@ -0,0 +1,23 @@ +local ropeSpots = { + 386, 421 +} + +function onCastSpell(creature, variant) + local position = creature:getPosition() + position:sendMagicEffect(CONST_ME_POFF) + + local tile = Tile(position) + if table.contains(ropeSpots, tile:getGround():getId()) then + tile = Tile(position:moveUpstairs()) + if tile then + creature:teleportTo(position) + position:sendMagicEffect(CONST_ME_TELEPORT) + else + creature:sendCancelMessage(RETURNVALUE_NOTENOUGHROOM) + end + else + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false + end + return true +end \ No newline at end of file diff --git a/data/spells/scripts/spells/magic shield.lua b/data/spells/scripts/spells/magic shield.lua new file mode 100644 index 0000000..86a5b4d --- /dev/null +++ b/data/spells/scripts/spells/magic shield.lua @@ -0,0 +1,11 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_MANASHIELD) +condition:setParameter(CONDITION_PARAM_TICKS, 200000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/mass healing.lua b/data/spells/scripts/spells/mass healing.lua new file mode 100644 index 0000000..21eb5de --- /dev/null +++ b/data/spells/scripts/spells/mass healing.lua @@ -0,0 +1,34 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +local healMonsters = true + +function onTargetCreature(creature, target) + if not healMonsters then + local master = target:getMaster() + if target:isMonster() and not master or master and master:isMonster() then + return true + end + end + + local player = creature:getPlayer() + + local base = 200 + local variation = 40 + + local value = math.random(-variation, variation) + base + local formula = 3 * player:getMagicLevel() + (2 * player:getLevel()) + local total = formula * value / 100 + + doTargetCombatHealth(0, target, COMBAT_HEALING, total, total, CONST_ME_NONE) + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/poison storm.lua b/data/spells/scripts/spells/poison storm.lua new file mode 100644 index 0000000..25ec6b8 --- /dev/null +++ b/data/spells/scripts/spells/poison storm.lua @@ -0,0 +1,18 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +function onTargetCreature(creature, target) + local player = Player(creature) + + local condition = Condition(CONDITION_POISON) + condition:setTiming(computeFormula(player:getLevel(), player:getMagicLevel(), 200, 50)) + target:addCondition(condition) +end + +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/strong haste.lua b/data/spells/scripts/spells/strong haste.lua new file mode 100644 index 0000000..9b08a62 --- /dev/null +++ b/data/spells/scripts/spells/strong haste.lua @@ -0,0 +1,12 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_HASTE) +condition:setParameter(CONDITION_PARAM_TICKS, 20000) +condition:setSpeedDelta(70) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/ultimate explosion.lua b/data/spells/scripts/spells/ultimate explosion.lua new file mode 100644 index 0000000..8c19f46 --- /dev/null +++ b/data/spells/scripts/spells/ultimate explosion.lua @@ -0,0 +1,23 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1) +combat:setParameter(COMBAT_PARAM_BLOCKSHIELD, 1) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_EXPLOSIONAREA) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +function onGetFormulaValues(player, level, maglevel) + local base = 250 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return -min, -max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/ultimate healing.lua b/data/spells/scripts/spells/ultimate healing.lua new file mode 100644 index 0000000..19aa349 --- /dev/null +++ b/data/spells/scripts/spells/ultimate healing.lua @@ -0,0 +1,22 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HEALING) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +function onGetFormulaValues(player, level, maglevel) + local base = 250 + local variation = 50 + + local formula = 3 * maglevel + (2 * level) + + local min = (formula * (base - variation)) / 100 + local max = (formula * (base + variation)) / 100 + return min, max +end + +combat:setCallback(CALLBACK_PARAM_LEVELMAGICVALUE, "onGetFormulaValues") + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/ultimate light.lua b/data/spells/scripts/spells/ultimate light.lua new file mode 100644 index 0000000..9679183 --- /dev/null +++ b/data/spells/scripts/spells/ultimate light.lua @@ -0,0 +1,13 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_BLUE) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false) + +local condition = Condition(CONDITION_LIGHT) +condition:setParameter(CONDITION_PARAM_LIGHT_LEVEL, 8) +condition:setParameter(CONDITION_PARAM_LIGHT_COLOR, 215) +condition:setParameter(CONDITION_PARAM_TICKS, (60 * 33 + 10) * 1000) +combat:setCondition(condition) + +function onCastSpell(creature, variant) + return combat:execute(creature, variant) +end \ No newline at end of file diff --git a/data/spells/scripts/spells/undead legion.lua b/data/spells/scripts/spells/undead legion.lua new file mode 100644 index 0000000..b5d9dd3 --- /dev/null +++ b/data/spells/scripts/spells/undead legion.lua @@ -0,0 +1,34 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREEN_RINGS) +combat:setArea(createCombatArea(AREA_CIRCLE5X5)) + +local humanBodies = { + 4240, 4241, 4247, 4248 +} + +function onCastSpell(creature, variant) + local position = Variant.getPosition(variant) + local tile = Tile(position) + if tile then + local corpse = tile:getTopDownItem() + if corpse then + local itemType = corpse:getType() + if not table.contains(humanBodies, itemType:getId()) then + if itemType:isCorpse() and itemType:isMovable() then + local monster = Game.createMonster("Skeleton", position) + if monster then + corpse:remove() + monster:setMaster(creature) + position:sendMagicEffect(CONST_ME_MAGIC_BLUE) + return true + end + end + end + end + end + + creature:getPosition():sendMagicEffect(CONST_ME_POFF) + creature:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return false +end \ No newline at end of file diff --git a/data/spells/spells.xml b/data/spells/spells.xml new file mode 100644 index 0000000..a2e5756 --- /dev/null +++ b/data/spells/spells.xml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/talkactions/lib/talkactions.lua b/data/talkactions/lib/talkactions.lua new file mode 100644 index 0000000..585eb19 --- /dev/null +++ b/data/talkactions/lib/talkactions.lua @@ -0,0 +1 @@ +-- Nothing -- diff --git a/data/talkactions/scripts/add_skill.lua b/data/talkactions/scripts/add_skill.lua new file mode 100644 index 0000000..f9b83e5 --- /dev/null +++ b/data/talkactions/scripts/add_skill.lua @@ -0,0 +1,66 @@ +local function getSkillId(skillName) + if skillName == "club" then + return SKILL_CLUB + elseif skillName == "sword" then + return SKILL_SWORD + elseif skillName == "axe" then + return SKILL_AXE + elseif skillName:sub(1, 4) == "dist" then + return SKILL_DISTANCE + elseif skillName:sub(1, 6) == "shield" then + return SKILL_SHIELD + elseif skillName:sub(1, 4) == "fish" then + return SKILL_FISHING + else + return SKILL_FIST + end +end + +local function getExpForLevel(level) + level = level - 1 + return ((50 * level * level * level) - (150 * level * level) + (400 * level)) / 3 +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 split = param:split(",") + if split[2] == nil then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + local target = Player(split[1]) + if target == nil then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + -- Trim left + split[2] = split[2]:gsub("^%s*(.-)$", "%1") + + local count = 1 + if split[3] ~= nil then + count = tonumber(split[3]) + end + + local ch = split[2]:sub(1, 1) + for i = 1, count do + if ch == "l" or ch == "e" then + target:addExperience(getExpForLevel(target:getLevel() + 1) - target:getExperience(), false) + elseif ch == "m" then + target:addManaSpent(target:getVocation():getRequiredManaSpent(target:getBaseMagicLevel() + 1) - target:getManaSpent()) + else + local skillId = getSkillId(split[2]) + target:addSkillTries(skillId, target:getVocation():getRequiredSkillTries(skillId, target:getSkillLevel(skillId) + 1) - target:getSkillTries(skillId)) + end + end + + return false +end diff --git a/data/talkactions/scripts/add_tutor.lua b/data/talkactions/scripts/add_tutor.lua new file mode 100644 index 0000000..621b6e8 --- /dev/null +++ b/data/talkactions/scripts/add_tutor.lua @@ -0,0 +1,21 @@ +function onSay(player, words, param) + if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + return true + end + + local target = Player(param) + if target == nil then + player:sendCancelMessage("A player with that name is not online.") + return false + end + + if target:getAccountType() ~= ACCOUNT_TYPE_NORMAL then + player:sendCancelMessage("You can only promote a normal player to a tutor.") + return false + end + + target:setAccountType(ACCOUNT_TYPE_TUTOR) + target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have been promoted to a tutor by " .. player:getName() .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have promoted " .. target:getName() .. " to a tutor.") + return false +end diff --git a/data/talkactions/scripts/animationeffect.lua b/data/talkactions/scripts/animationeffect.lua new file mode 100644 index 0000000..ba55558 --- /dev/null +++ b/data/talkactions/scripts/animationeffect.lua @@ -0,0 +1,29 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local effect = tonumber(param) + local position = player:getPosition() + local toPositionLow = {z = position.z} + local toPositionHigh = {z = position.z} + + toPositionLow.x = position.x - 7 + toPositionHigh.x = position.x + 7 + for i = -5, 5 do + toPositionLow.y = position.y + i + toPositionHigh.y = toPositionLow.y + position:sendDistanceEffect(toPositionLow, effect) + position:sendDistanceEffect(toPositionHigh, effect) + end + + toPositionLow.y = position.y - 5 + toPositionHigh.y = position.y + 5 + for i = -6, 6 do + toPositionLow.x = position.x + i + toPositionHigh.x = toPositionLow.x + position:sendDistanceEffect(toPositionLow, effect) + position:sendDistanceEffect(toPositionHigh, effect) + end + return false +end diff --git a/data/talkactions/scripts/ban.lua b/data/talkactions/scripts/ban.lua new file mode 100644 index 0000000..fc46763 --- /dev/null +++ b/data/talkactions/scripts/ban.lua @@ -0,0 +1,39 @@ +local banDays = 7 + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local name = param + local reason = '' + + local separatorPos = param:find(',') + if separatorPos ~= nil then + name = param:sub(0, separatorPos - 1) + reason = string.trim(param:sub(separatorPos + 1)) + end + + local accountId = getAccountNumberByPlayerName(name) + if accountId == 0 then + return false + end + + local resultId = db.storeQuery("SELECT 1 FROM `account_bans` WHERE `account_id` = " .. accountId) + if resultId ~= false then + result.free(resultId) + return false + end + + local timeNow = os.time() + db.query("INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" .. + accountId .. ", " .. db.escapeString(reason) .. ", " .. timeNow .. ", " .. timeNow + (banDays * 86400) .. ", " .. player:getGuid() .. ")") + + local target = Player(name) + if target ~= nil then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, target:getName() .. " has been banned.") + target:remove() + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, name .. " has been banned.") + end +end diff --git a/data/talkactions/scripts/broadcast.lua b/data/talkactions/scripts/broadcast.lua new file mode 100644 index 0000000..a6e3933 --- /dev/null +++ b/data/talkactions/scripts/broadcast.lua @@ -0,0 +1,11 @@ +function onSay(player, words, param) + if not getPlayerFlagValue(player, PlayerFlag_CanBroadcast) then + return true + end + + print("> " .. player:getName() .. " broadcasted: \"" .. param .. "\".") + for _, targetPlayer in ipairs(Game.getPlayers()) do + targetPlayer:sendTextMessage(MESSAGE_STATUS_WARNING, param) + end + return false +end diff --git a/data/talkactions/scripts/buyhouse.lua b/data/talkactions/scripts/buyhouse.lua new file mode 100644 index 0000000..5e3020b --- /dev/null +++ b/data/talkactions/scripts/buyhouse.lua @@ -0,0 +1,36 @@ +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.") + return false +end diff --git a/data/talkactions/scripts/buyprem.lua b/data/talkactions/scripts/buyprem.lua new file mode 100644 index 0000000..1aa4f5f --- /dev/null +++ b/data/talkactions/scripts/buyprem.lua @@ -0,0 +1,25 @@ +local config = { + days = 90, + maxDays = 365, + price = 10000 +} + +function onSay(player, words, param) + if configManager.getBoolean(configKeys.FREE_PREMIUM) then + return true + end + + if player:getPremiumDays() <= config.maxDays then + if player:removeMoney(config.price) then + player:addPremiumDays(config.days) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have bought " .. config.days .." days of premium account.") + else + player:sendCancelMessage("You don't have enough money, " .. config.maxDays .. " days premium account costs " .. config.price .. " gold coins.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + else + player:sendCancelMessage("You can not buy more than " .. config.maxDays .. " days of premium account.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/chameleon.lua b/data/talkactions/scripts/chameleon.lua new file mode 100644 index 0000000..d3fcbe2 --- /dev/null +++ b/data/talkactions/scripts/chameleon.lua @@ -0,0 +1,25 @@ +local condition = Condition(CONDITION_OUTFIT, CONDITIONID_COMBAT) +condition:setTicks(-1) + +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 itemType = ItemType(param) + if itemType:getId() == 0 then + itemType = ItemType(tonumber(param)) + if itemType:getId() == 0 then + player:sendCancelMessage("There is no item with that id or name.") + return false + end + end + + condition:setOutfit(itemType:getId()) + player:addCondition(condition) + return false +end diff --git a/data/talkactions/scripts/changesex.lua b/data/talkactions/scripts/changesex.lua new file mode 100644 index 0000000..c8afee9 --- /dev/null +++ b/data/talkactions/scripts/changesex.lua @@ -0,0 +1,19 @@ +local premiumDaysCost = 3 + +function onSay(player, words, param) + if player:getGroup():getAccess() then + player:setSex(player:getSex() == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have changed your sex.") + return false + end + + if player:getPremiumDays() >= premiumDaysCost then + player:removePremiumDays(premiumDaysCost) + player:setSex(player:getSex() == PLAYERSEX_FEMALE and PLAYERSEX_MALE or PLAYERSEX_FEMALE) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have changed your sex for ".. premiumDaysCost .." days of your premium account.") + else + player:sendCancelMessage("You do not have enough premium days, changing sex costs ".. premiumDaysCost .." days of your premium account.") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/clean.lua b/data/talkactions/scripts/clean.lua new file mode 100644 index 0000000..0d5f7aa --- /dev/null +++ b/data/talkactions/scripts/clean.lua @@ -0,0 +1,15 @@ +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 itemCount = cleanMap() + if itemCount > 0 then + player:sendTextMessage(MESSAGE_STATUS_WARNING, "Cleaned " .. itemCount .. " item" .. (itemCount > 1 and "s" or "") .. " from the map.") + end + return false +end diff --git a/data/talkactions/scripts/closeserver.lua b/data/talkactions/scripts/closeserver.lua new file mode 100644 index 0000000..2f7c95e --- /dev/null +++ b/data/talkactions/scripts/closeserver.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + if param == "shutdown" then + Game.setGameState(GAME_STATE_SHUTDOWN) + else + Game.setGameState(GAME_STATE_CLOSED) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now closed.") + end + return false +end diff --git a/data/talkactions/scripts/create_item.lua b/data/talkactions/scripts/create_item.lua new file mode 100644 index 0000000..ddc37e6 --- /dev/null +++ b/data/talkactions/scripts/create_item.lua @@ -0,0 +1,59 @@ +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 split = param:split(",") + + local itemType = ItemType(split[1]) + if itemType:getId() == 0 then + itemType = ItemType(tonumber(split[1])) + if itemType:getId() == 0 then + player:sendCancelMessage("There is no item with that id or name.") + return false + end + end + + local keynumber = 0 + local count = tonumber(split[2]) + if count ~= nil then + if itemType:isStackable() then + count = math.min(10000, math.max(1, count)) + elseif itemType:isKey() then + keynumber = count + count = 1 + elseif not itemType:hasSubType() then + count = math.min(100, math.max(1, count)) + else + count = math.max(1, count) + end + else + count = 1 + end + + local result = player:addItem(itemType:getId(), count) + if result ~= nil then + if not itemType:isStackable() then + if type(result) == "table" then + for _, item in ipairs(result) do + if itemType:isKey() then + item:setAttribute(ITEM_ATTRIBUTE_KEYNUMBER, keynumber) + end + item:decay() + end + else + if itemType:isKey() then + result:setAttribute(ITEM_ATTRIBUTE_KEYNUMBER, keynumber) + end + result:decay() + end + end + + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + end + return false +end diff --git a/data/talkactions/scripts/deathlist.lua b/data/talkactions/scripts/deathlist.lua new file mode 100644 index 0000000..c1066a9 --- /dev/null +++ b/data/talkactions/scripts/deathlist.lua @@ -0,0 +1,62 @@ +local function getArticle(str) + return str:find("[AaEeIiOoUuYy]") == 1 and "an" or "a" +end + +local function getMonthDayEnding(day) + if day == "01" or day == "21" or day == "31" then + return "st" + elseif day == "02" or day == "22" then + return "nd" + elseif day == "03" or day == "23" then + return "rd" + else + return "th" + end +end + +local function getMonthString(m) + return os.date("%B", os.time{year = 1970, month = m, day = 1}) +end + +function onSay(player, words, param) + local resultId = db.storeQuery("SELECT `id`, `name` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId ~= false then + local targetGUID = result.getDataInt(resultId, "id") + local targetName = result.getDataString(resultId, "name") + result.free(resultId) + local str = "" + local breakline = "" + + local resultId = db.storeQuery("SELECT `time`, `level`, `killed_by`, `is_player` FROM `player_deaths` WHERE `player_id` = " .. targetGUID .. " ORDER BY `time` DESC") + if resultId ~= false then + repeat + if str ~= "" then + breakline = "\n" + end + local date = os.date("*t", result.getDataInt(resultId, "time")) + + local article = "" + local killed_by = result.getDataString(resultId, "killed_by") + if result.getDataInt(resultId, "is_player") == 0 then + article = getArticle(killed_by) .. " " + killed_by = string.lower(killed_by) + end + + if date.day < 10 then date.day = "0" .. date.day end + if date.hour < 10 then date.hour = "0" .. date.hour end + if date.min < 10 then date.min = "0" .. date.min end + if date.sec < 10 then date.sec = "0" .. date.sec end + str = str .. breakline .. " " .. date.day .. getMonthDayEnding(date.day) .. " " .. getMonthString(date.month) .. " " .. date.year .. " " .. date.hour .. ":" .. date.min .. ":" .. date.sec .. " Died at Level " .. result.getDataInt(resultId, "level") .. " by " .. article .. killed_by .. "." + until not result.next(resultId) + result.free(resultId) + end + + if str == "" then + str = "No deaths." + end + player:popupFYI("Deathlist for player, " .. targetName .. ".\n\n" .. str) + else + player:sendCancelMessage("A player with that name does not exist.") + end + return false +end diff --git a/data/talkactions/scripts/down.lua b/data/talkactions/scripts/down.lua new file mode 100644 index 0000000..7a1d986 --- /dev/null +++ b/data/talkactions/scripts/down.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position.z = position.z + 1 + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/data/talkactions/scripts/experienceshare.lua b/data/talkactions/scripts/experienceshare.lua new file mode 100644 index 0000000..320d577 --- /dev/null +++ b/data/talkactions/scripts/experienceshare.lua @@ -0,0 +1,28 @@ +function onSay(player, words, param) + local party = player:getParty() + if not party then + player:sendCancelMessage("You are not part of a party.") + return false + end + + if party:getLeader() ~= player then + player:sendCancelMessage("You are not the leader of the party.") + return false + end + + if party:isSharedExperienceActive() then + if player:getCondition(CONDITION_INFIGHT) then + player:sendCancelMessage("You are in fight. Experience sharing not disabled.") + else + party:setSharedExperience(false) + end + else + if player:getCondition(CONDITION_INFIGHT) then + player:sendCancelMessage("You are in fight. Experience sharing not enabled.") + else + party:setSharedExperience(true) + end + end + + return false +end diff --git a/data/talkactions/scripts/ghost.lua b/data/talkactions/scripts/ghost.lua new file mode 100644 index 0000000..e314c73 --- /dev/null +++ b/data/talkactions/scripts/ghost.lua @@ -0,0 +1,23 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GAMEMASTER then + return false + end + + local position = player:getPosition() + local isGhost = not player:isInGhostMode() + + player:setGhostMode(isGhost) + if isGhost then + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are now invisible.") + position:sendMagicEffect(CONST_ME_POFF) + else + player:sendTextMessage(MESSAGE_INFO_DESCR, "You are visible again.") + position.x = position.x + 1 + position:sendMagicEffect(CONST_ME_TELEPORT) + end + return false +end diff --git a/data/talkactions/scripts/info.lua b/data/talkactions/scripts/info.lua new file mode 100644 index 0000000..8969b1c --- /dev/null +++ b/data/talkactions/scripts/info.lua @@ -0,0 +1,37 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local target = Player(param) + if not target then + player:sendCancelMessage("Player not found.") + return false + end + + if target:getAccountType() > player:getAccountType() then + player:sendCancelMessage("You can not get info about this player.") + return false + end + + local targetIp = target:getIp() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Name: " .. target:getName()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Access: " .. (target:getGroup():getAccess() and "1" or "0")) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Level: " .. target:getLevel()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Magic Level: " .. target:getMagicLevel()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Speed: " .. target:getSpeed()) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Position: " .. string.format("(%0.5d / %0.5d / %0.3d)", target:getPosition().x, target:getPosition().y, target:getPosition().z)) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "IP: " .. Game.convertIpToString(targetIp)) + + local players = {} + for _, targetPlayer in ipairs(Game.getPlayers()) do + if targetPlayer:getIp() == targetIp and targetPlayer ~= target then + players[#players + 1] = targetPlayer:getName() .. " [" .. targetPlayer:getLevel() .. "]" + end + end + + if #players > 0 then + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Other players on same IP: " .. table.concat(players, ", ") .. ".") + end + return false +end diff --git a/data/talkactions/scripts/ipban.lua b/data/talkactions/scripts/ipban.lua new file mode 100644 index 0000000..1a393e2 --- /dev/null +++ b/data/talkactions/scripts/ipban.lua @@ -0,0 +1,36 @@ +local ipBanDays = 7 + +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + return false + end + + local targetIp = result.getDataLong(resultId, "lastip") + result.free(resultId) + + local targetPlayer = Player(param) + if targetPlayer then + targetIp = targetPlayer:getIp() + targetPlayer:remove() + end + + if targetIp == 0 then + return false + end + + resultId = db.storeQuery("SELECT 1 FROM `ip_bans` WHERE `ip` = " .. targetIp) + if resultId ~= false then + result.free(resultId) + return false + end + + local timeNow = os.time() + db.query("INSERT INTO `ip_bans` (`ip`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES (" .. + targetIp .. ", '', " .. timeNow .. ", " .. timeNow + (ipBanDays * 86400) .. ", " .. player:getGuid() .. ")") + return false +end diff --git a/data/talkactions/scripts/kick.lua b/data/talkactions/scripts/kick.lua new file mode 100644 index 0000000..67f8ec4 --- /dev/null +++ b/data/talkactions/scripts/kick.lua @@ -0,0 +1,19 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local target = Player(param) + if target == nil then + player:sendCancelMessage("Player not found.") + return false + end + + if target:getGroup():getAccess() then + player:sendCancelMessage("You cannot kick this player.") + return false + end + + target:remove() + return false +end diff --git a/data/talkactions/scripts/kills.lua b/data/talkactions/scripts/kills.lua new file mode 100644 index 0000000..abdec2d --- /dev/null +++ b/data/talkactions/scripts/kills.lua @@ -0,0 +1,75 @@ +function onSay(player, words, param) + if Game.getWorldType() == WORLD_TYPE_PVP_ENFORCED then + player:showTextDialog(1998, "Your character has not murders history.", false) + return false + end + + local today = os.time() + local skullTicks = player:getPlayerKillerEnd() + local lastDay = 0 + local lastWeek = 0 + local lastMonth = 0 + local egibleMurders = 0 + local dayTimestamp = today - (24 * 60 * 60) + local weekTimestamp = today - (7 * 24 * 60 * 60) + local monthTimestamp = today - (30 * 24 * 60 * 60) + + local killsDayRedSkull = configManager.getNumber(configKeys.KILLS_DAY_RED_SKULL) + local killsWeekRedSkull = configManager.getNumber(configKeys.KILLS_WEEK_RED_SKULL) + local killsMonthRedSkull = configManager.getNumber(configKeys.KILLS_MONTH_RED_SKULL) + + local killsDayBanishment = configManager.getNumber(configKeys.KILLS_DAY_BANISHMENT) + local killsWeekBanishment = configManager.getNumber(configKeys.KILLS_WEEK_BANISHMENT) + local killsMonthBanishment = configManager.getNumber(configKeys.KILLS_MONTH_BANISHMENT) + + for _, timestamp in pairs(player:getMurderTimestamps()) do + if timestamp > dayTimestamp then + lastDay = lastDay + 1 + end + + if timestamp > weekTimestamp then + lastWeek = lastWeek + 1 + end + + egibleMurders = lastMonth + 1 + + if timestamp <= monthTimestamp then + egibleMurders = lastMonth + end + + lastMonth = egibleMurders + end + + local message = "" + message = message .. "Default murders\n" + message = message .. "- Daily kills for red skull " .. killsDayRedSkull .. "\n" + message = message .. "- Weekly kills for red skull " .. killsWeekRedSkull .. "\n" + message = message .. "- Monthly kills for red skull " .. killsMonthRedSkull .. "\n" + + message = message .. "- Daily kills for banishment " .. killsDayBanishment .. "\n" + message = message .. "- Weekly kills for banishment " .. killsWeekBanishment .. "\n" + message = message .. "- Monthly kills for banishment " .. killsMonthBanishment .. "\n" + + message = message .. "\n" + + message = message .. "Last murders within 24 hours " .. lastDay .. "\n" + message = message .. "Last murders within a week " .. lastDay .. "\n" + message = message .. "Last murders within a month " .. lastDay .. "\n" + + message = message .. "\n" + + message = message .. "Players you may kill for a red skull:\n" + message = message .. "- Within 24 hours " .. killsDayRedSkull - lastDay .. " murders.\n" + message = message .. "- Within a week " .. killsWeekRedSkull - lastWeek .. " murders.\n" + message = message .. "- Within a month " .. killsMonthRedSkull - lastDay .. " murders.\n" + + message = message .. "\n" + + message = message .. "Players you may kill for a banishment:\n" + message = message .. "- Within 24 hours " .. killsDayBanishment - lastDay .. " murders.\n" + message = message .. "- Within a week " .. killsWeekBanishment - lastWeek .. " murders.\n" + message = message .. "- Within a month " .. killsMonthBanishment - lastDay .. " murders.\n" + + player:showTextDialog(1998, message, false) + return false +end diff --git a/data/talkactions/scripts/leavehouse.lua b/data/talkactions/scripts/leavehouse.lua new file mode 100644 index 0000000..18938cc --- /dev/null +++ b/data/talkactions/scripts/leavehouse.lua @@ -0,0 +1,21 @@ +function onSay(player, words, param) + 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 + + if house:getOwnerGuid() ~= player:getGuid() then + player:sendCancelMessage("You are not the owner of this house.") + position:sendMagicEffect(CONST_ME_POFF) + return false + end + + house:setOwnerGuid(0) + player:sendTextMessage(MESSAGE_INFO_DESCR, "You have successfully left your house.") + position:sendMagicEffect(CONST_ME_POFF) + return false +end diff --git a/data/talkactions/scripts/looktype.lua b/data/talkactions/scripts/looktype.lua new file mode 100644 index 0000000..1a1b11d --- /dev/null +++ b/data/talkactions/scripts/looktype.lua @@ -0,0 +1,15 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local lookType = tonumber(param) + if lookType >= 0 and lookType ~= 1 and lookType ~= 135 and lookType ~= 411 and lookType ~= 415 and lookType ~= 424 and (lookType <= 160 or lookType >= 192) and lookType ~= 439 and lookType ~= 440 and lookType ~= 468 and lookType ~= 469 and (lookType < 474 or lookType > 485) and lookType ~= 501 and lookType ~= 518 and lookType ~= 519 and lookType ~= 520 and lookType ~= 524 and lookType ~= 525 and lookType ~= 536 and lookType ~= 543 and lookType ~= 549 and lookType ~= 576 and lookType ~= 581 and lookType ~= 582 and lookType ~= 597 and lookType ~= 616 and lookType ~= 623 and lookType ~= 625 and (lookType <= 637 or lookType >= 644) and (lookType <= 644 or lookType >= 647) and (lookType <= 651 or lookType >= 664) and lookType <= 699 then + local playerOutfit = player:getOutfit() + playerOutfit.lookType = lookType + player:setOutfit(playerOutfit) + else + player:sendCancelMessage("A look type with that id does not exist.") + end + return false +end diff --git a/data/talkactions/scripts/magiceffect.lua b/data/talkactions/scripts/magiceffect.lua new file mode 100644 index 0000000..ebc26b3 --- /dev/null +++ b/data/talkactions/scripts/magiceffect.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + player:getPosition():sendMagicEffect(tonumber(param)) + return false +end diff --git a/data/talkactions/scripts/mccheck.lua b/data/talkactions/scripts/mccheck.lua new file mode 100644 index 0000000..13ffbc9 --- /dev/null +++ b/data/talkactions/scripts/mccheck.lua @@ -0,0 +1,40 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Multiclient Check List:") + + local ipList = {} + local players = Game.getPlayers() + for i = 1, #players do + local tmpPlayer = players[i] + local ip = tmpPlayer:getIp() + if ip ~= 0 then + local list = ipList[ip] + if not list then + ipList[ip] = {} + list = ipList[ip] + end + list[#list + 1] = tmpPlayer + end + end + + for ip, list in pairs(ipList) do + local listLength = #list + if listLength > 1 then + local tmpPlayer = list[1] + local message = ("%s: %s [%d]"):format(Game.convertIpToString(ip), tmpPlayer:getName(), tmpPlayer:getLevel()) + for i = 2, listLength do + tmpPlayer = list[i] + message = ("%s, %s [%d]"):format(message, tmpPlayer:getName(), tmpPlayer:getLevel()) + end + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, message .. ".") + end + end + return false +end diff --git a/data/talkactions/scripts/online.lua b/data/talkactions/scripts/online.lua new file mode 100644 index 0000000..705bbb3 --- /dev/null +++ b/data/talkactions/scripts/online.lua @@ -0,0 +1,23 @@ +local maxPlayersPerMessage = 10 + +function onSay(player, words, param) + local hasAccess = player:getGroup():getAccess() + local players = Game.getPlayers() + local onlineList = {} + + for _, targetPlayer in ipairs(players) do + if hasAccess or not targetPlayer:isInGhostMode() then + table.insert(onlineList, ("%s [%d]"):format(targetPlayer:getName(), targetPlayer:getLevel())) + end + end + + local playersOnline = #onlineList + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, ("%d players online."):format(playersOnline)) + + for i = 1, playersOnline, maxPlayersPerMessage do + local j = math.min(i + maxPlayersPerMessage - 1, playersOnline) + local msg = table.concat(onlineList, ", ", i, j) .. "." + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, msg) + end + return false +end \ No newline at end of file diff --git a/data/talkactions/scripts/openserver.lua b/data/talkactions/scripts/openserver.lua new file mode 100644 index 0000000..c3896e7 --- /dev/null +++ b/data/talkactions/scripts/openserver.lua @@ -0,0 +1,13 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + if player:getAccountType() < ACCOUNT_TYPE_GOD then + return false + end + + Game.setGameState(GAME_STATE_NORMAL) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server is now open.") + return false +end diff --git a/data/talkactions/scripts/owner.lua b/data/talkactions/scripts/owner.lua new file mode 100644 index 0000000..d56f489 --- /dev/null +++ b/data/talkactions/scripts/owner.lua @@ -0,0 +1,30 @@ +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 tile = Tile(player:getPosition()) + local house = tile and tile:getHouse() + if house == nil then + player:sendCancelMessage("You are not inside a house.") + return false + end + + if param == "" or param == "none" then + house:setOwnerGuid(0) + return false + end + + local targetPlayer = Player(param) + if targetPlayer == nil then + player:sendCancelMessage("Player not found.") + return false + end + + house:setOwnerGuid(targetPlayer:getGuid()) + return false +end diff --git a/data/talkactions/scripts/place_monster.lua b/data/talkactions/scripts/place_monster.lua new file mode 100644 index 0000000..8446c0b --- /dev/null +++ b/data/talkactions/scripts/place_monster.lua @@ -0,0 +1,20 @@ +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 monster = Game.createMonster(param, position) + if monster ~= nil then + monster:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/place_npc.lua b/data/talkactions/scripts/place_npc.lua new file mode 100644 index 0000000..aaf6ef6 --- /dev/null +++ b/data/talkactions/scripts/place_npc.lua @@ -0,0 +1,20 @@ +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 npc = Game.createNpc(param, position) + if npc ~= nil then + npc:setMasterPos(position) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/place_summon.lua b/data/talkactions/scripts/place_summon.lua new file mode 100644 index 0000000..f511d20 --- /dev/null +++ b/data/talkactions/scripts/place_summon.lua @@ -0,0 +1,20 @@ +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 monster = Game.createMonster(param, position) + if monster ~= nil then + monster:setMaster(player) + position:sendMagicEffect(CONST_ME_MAGIC_RED) + else + player:sendCancelMessage("There is not enough room.") + position:sendMagicEffect(CONST_ME_POFF) + end + return false +end diff --git a/data/talkactions/scripts/position.lua b/data/talkactions/scripts/position.lua new file mode 100644 index 0000000..299ce6e --- /dev/null +++ b/data/talkactions/scripts/position.lua @@ -0,0 +1,10 @@ +function onSay(player, words, param) + if player:getGroup():getAccess() and param ~= "" then + local split = param:split(",") + player:teleportTo(Position(split[1], split[2], split[3])) + else + local position = player:getPosition() + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Your current position is: " .. position.x .. ", " .. position.y .. ", " .. position.z .. ".") + end + return false +end diff --git a/data/talkactions/scripts/remove_tutor.lua b/data/talkactions/scripts/remove_tutor.lua new file mode 100644 index 0000000..27b0bdb --- /dev/null +++ b/data/talkactions/scripts/remove_tutor.lua @@ -0,0 +1,27 @@ +function onSay(player, words, param) + if player:getAccountType() <= ACCOUNT_TYPE_TUTOR then + return true + end + + local resultId = db.storeQuery("SELECT `name`, `account_id`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + player:sendCancelMessage("A player with that name does not exist.") + return false + end + + if result.getDataInt(resultId, "account_type") ~= ACCOUNT_TYPE_TUTOR then + player:sendCancelMessage("You can only demote a tutor to a normal player.") + return false + end + + local target = Player(param) + if target ~= nil then + target:setAccountType(ACCOUNT_TYPE_NORMAL) + else + db.query("UPDATE `accounts` SET `type` = " .. ACCOUNT_TYPE_NORMAL .. " WHERE `id` = " .. result.getDataInt(resultId, "account_id")) + end + + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have demoted " .. result.getDataString(resultId, "name") .. " to a normal player.") + result.free(resultId) + return false +end diff --git a/data/talkactions/scripts/removething.lua b/data/talkactions/scripts/removething.lua new file mode 100644 index 0000000..8216e03 --- /dev/null +++ b/data/talkactions/scripts/removething.lua @@ -0,0 +1,33 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection()) + + local tile = Tile(position) + if not tile then + player:sendCancelMessage("Object not found.") + return false + end + + local thing = tile:getTopVisibleThing(player) + if not thing then + player:sendCancelMessage("Thing not found.") + return false + end + + if thing:isCreature() then + thing:remove() + elseif thing:isItem() then + if thing == tile:getGround() then + player:sendCancelMessage("You may not remove a ground tile.") + return false + end + thing:remove(tonumber(param) or -1) + end + + position:sendMagicEffect(CONST_ME_MAGIC_RED) + return false +end diff --git a/data/talkactions/scripts/serverinfo.lua b/data/talkactions/scripts/serverinfo.lua new file mode 100644 index 0000000..6b26b98 --- /dev/null +++ b/data/talkactions/scripts/serverinfo.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Server Info:" + .. "\nExp rate: " .. Game.getExperienceStage(player:getLevel()) + .. "\nSkill rate: " .. configManager.getNumber(configKeys.RATE_SKILL) + .. "\nMagic rate: " .. configManager.getNumber(configKeys.RATE_MAGIC) + .. "\nLoot rate: " .. configManager.getNumber(configKeys.RATE_LOOT)) + return false +end diff --git a/data/talkactions/scripts/storagevalue.lua b/data/talkactions/scripts/storagevalue.lua new file mode 100644 index 0000000..6ed9bb9 --- /dev/null +++ b/data/talkactions/scripts/storagevalue.lua @@ -0,0 +1,15 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local split = param:split(",") + if split[2] == nil then + player:sendCancelMessage("Insufficient parameters.") + return false + end + + player:setStorageValue(tonumber(split[1]), tonumber(split[2])) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "[Storage Value] " .. split[1] .. " changed it's value to " .. split[2] .. ".") + return false +end diff --git a/data/talkactions/scripts/teleport_creature_here.lua b/data/talkactions/scripts/teleport_creature_here.lua new file mode 100644 index 0000000..ea36919 --- /dev/null +++ b/data/talkactions/scripts/teleport_creature_here.lua @@ -0,0 +1,24 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local creature = Creature(param) + if not creature then + player:sendCancelMessage("A creature with that name could not be found.") + return false + end + + local oldPosition = creature:getPosition() + local newPosition = creature:getClosestFreePosition(player:getPosition(), false) + if newPosition.x == 0 then + player:sendCancelMessage("You can not teleport " .. creature:getName() .. ".") + return false + elseif creature:teleportTo(newPosition) then + if not creature:isInGhostMode() then + oldPosition:sendMagicEffect(CONST_ME_POFF) + newPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + end + return false +end diff --git a/data/talkactions/scripts/teleport_home.lua b/data/talkactions/scripts/teleport_home.lua new file mode 100644 index 0000000..6485230 --- /dev/null +++ b/data/talkactions/scripts/teleport_home.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + player:teleportTo(player:getTown():getTemplePosition()) + return false +end diff --git a/data/talkactions/scripts/teleport_ntiles.lua b/data/talkactions/scripts/teleport_ntiles.lua new file mode 100644 index 0000000..4ffc5b5 --- /dev/null +++ b/data/talkactions/scripts/teleport_ntiles.lua @@ -0,0 +1,28 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local steps = tonumber(param) + if not steps then + return false + end + + local position = player:getPosition() + position:getNextPosition(player:getDirection(), steps) + + position = player:getClosestFreePosition(position, false) + if position.x == 0 then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/data/talkactions/scripts/teleport_to_creature.lua b/data/talkactions/scripts/teleport_to_creature.lua new file mode 100644 index 0000000..17a7da9 --- /dev/null +++ b/data/talkactions/scripts/teleport_to_creature.lua @@ -0,0 +1,14 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local target = Creature(param) + if target == nil then + player:sendCancelMessage("Creature not found.") + return false + end + + player:teleportTo(target:getPosition()) + return false +end diff --git a/data/talkactions/scripts/teleport_to_pos.lua b/data/talkactions/scripts/teleport_to_pos.lua new file mode 100644 index 0000000..8068e21 --- /dev/null +++ b/data/talkactions/scripts/teleport_to_pos.lua @@ -0,0 +1,9 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local split = param:split(",") + local position = {x = split[1], y = split[2], z = split[3]} + return player:teleportTo(position) +end diff --git a/data/talkactions/scripts/teleport_to_town.lua b/data/talkactions/scripts/teleport_to_town.lua new file mode 100644 index 0000000..87cdfae --- /dev/null +++ b/data/talkactions/scripts/teleport_to_town.lua @@ -0,0 +1,18 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local town = Town(param) + if town == nil then + town = Town(tonumber(param)) + end + + if town == nil then + player:sendCancelMessage("Town not found.") + return false + end + + player:teleportTo(town:getTemplePosition()) + return false +end diff --git a/data/talkactions/scripts/unban.lua b/data/talkactions/scripts/unban.lua new file mode 100644 index 0000000..b65c4c7 --- /dev/null +++ b/data/talkactions/scripts/unban.lua @@ -0,0 +1,16 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local resultId = db.storeQuery("SELECT `account_id`, `lastip` FROM `players` WHERE `name` = " .. db.escapeString(param)) + if resultId == false then + return false + end + + db.asyncQuery("DELETE FROM `account_bans` WHERE `account_id` = " .. result.getDataInt(resultId, "account_id")) + db.asyncQuery("DELETE FROM `ip_bans` WHERE `ip` = " .. result.getDataInt(resultId, "lastip")) + result.free(resultId) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, param .. " has been unbanned.") + return false +end diff --git a/data/talkactions/scripts/up.lua b/data/talkactions/scripts/up.lua new file mode 100644 index 0000000..de3477f --- /dev/null +++ b/data/talkactions/scripts/up.lua @@ -0,0 +1,17 @@ +function onSay(player, words, param) + if not player:getGroup():getAccess() then + return true + end + + local position = player:getPosition() + position.z = position.z - 1 + + local tile = Tile(position) + if tile == nil or tile:getGround() == nil then + player:sendCancelMessage("You cannot teleport there.") + return false + end + + player:teleportTo(position) + return false +end diff --git a/data/talkactions/scripts/uptime.lua b/data/talkactions/scripts/uptime.lua new file mode 100644 index 0000000..7c0e291 --- /dev/null +++ b/data/talkactions/scripts/uptime.lua @@ -0,0 +1,8 @@ +function onSay(player, words, param) + local uptime = getWorldUpTime() + + local hours = math.floor(uptime / 3600) + local minutes = math.floor((uptime - (3600 * hours)) / 60) + player:sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Uptime: " .. hours .. " hours and " .. minutes .. " minutes.") + return false +end diff --git a/data/talkactions/talkactions.xml b/data/talkactions/talkactions.xml new file mode 100644 index 0000000..ad15b22 --- /dev/null +++ b/data/talkactions/talkactions.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/houses.xml b/data/world/houses.xml new file mode 100644 index 0000000..e7161eb --- /dev/null +++ b/data/world/houses.xml @@ -0,0 +1,864 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/world/map.otbm b/data/world/map.otbm new file mode 100644 index 0000000..701d9dc Binary files /dev/null and b/data/world/map.otbm differ diff --git a/data/world/spawns.xml b/data/world/spawns.xml new file mode 100644 index 0000000..cfb8db5 --- /dev/null +++ b/data/world/spawns.xml @@ -0,0 +1,41587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..5d6116e --- /dev/null +++ b/database.sql @@ -0,0 +1,605 @@ +-- phpMyAdmin SQL Dump +-- version 4.0.5 +-- http://www.phpmyadmin.net +-- +-- Host: 127.0.0.1:3306 + +-- Generation Time: May 17, 2017 at 12:09 AM +-- Server version: 5.5.33 +-- PHP Version: 5.4.19 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + +-- +-- Database: `neloria` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `accounts` +-- + +CREATE TABLE `accounts` ( + `id` int(11) NOT NULL, + `password` char(40) NOT NULL, + `type` int(11) NOT NULL DEFAULT '1', + `premdays` int(11) NOT NULL DEFAULT '0', + `lastday` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `accounts` +-- + +INSERT INTO `accounts` (`id`, `password`, `type`, `premdays`, `lastday`) VALUES +(1234567, '41da8bef22aaef9d7c5821fa0f0de7cccc4dda4d', 5, 600, 1494975990); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_bans` +-- + +CREATE TABLE `account_bans` ( + `account_id` int(11) NOT NULL, + `reason` varchar(255) NOT NULL, + `banned_at` bigint(20) NOT NULL, + `expires_at` bigint(20) NOT NULL, + `banned_by` int(11) NOT NULL, + PRIMARY KEY (`account_id`), + KEY `banned_by` (`banned_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_ban_history` +-- + +CREATE TABLE `account_ban_history` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL, + `reason` varchar(255) NOT NULL, + `banned_at` bigint(20) NOT NULL, + `expired_at` bigint(20) NOT NULL, + `banned_by` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `account_id` (`account_id`), + KEY `banned_by` (`banned_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `account_viplist` +-- + +CREATE TABLE `account_viplist` ( + `account_id` int(11) NOT NULL COMMENT 'id of account whose viplist entry it is', + `player_id` int(11) NOT NULL COMMENT 'id of target player of viplist entry', + `description` varchar(128) NOT NULL DEFAULT '', + `icon` tinyint(2) unsigned NOT NULL DEFAULT '0', + `notify` tinyint(1) NOT NULL DEFAULT '0', + UNIQUE KEY `account_player_index` (`account_id`,`player_id`), + KEY `player_id` (`player_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guilds` +-- + +CREATE TABLE `guilds` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `ownerid` int(11) NOT NULL, + `creationdata` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + UNIQUE KEY `ownerid` (`ownerid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- +-- Triggers `guilds` +-- +DROP TRIGGER IF EXISTS `oncreate_guilds`; +DELIMITER // +CREATE TRIGGER `oncreate_guilds` AFTER INSERT ON `guilds` + FOR EACH ROW BEGIN + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('the Leader', 3, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Vice-Leader', 2, NEW.`id`); + INSERT INTO `guild_ranks` (`name`, `level`, `guild_id`) VALUES ('a Member', 1, NEW.`id`); +END +// +DELIMITER ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guildwar_kills` +-- + +CREATE TABLE `guildwar_kills` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `killer` varchar(50) NOT NULL, + `target` varchar(50) NOT NULL, + `killerguild` int(11) NOT NULL DEFAULT '0', + `targetguild` int(11) NOT NULL DEFAULT '0', + `warid` int(11) NOT NULL DEFAULT '0', + `time` bigint(15) NOT NULL, + PRIMARY KEY (`id`), + KEY `warid` (`warid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=8 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_invites` +-- + +CREATE TABLE `guild_invites` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `guild_id` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`,`guild_id`), + KEY `guild_id` (`guild_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_membership` +-- + +CREATE TABLE `guild_membership` ( + `player_id` int(11) NOT NULL, + `guild_id` int(11) NOT NULL, + `rank_id` int(11) NOT NULL, + `nick` varchar(15) NOT NULL DEFAULT '', + PRIMARY KEY (`player_id`), + KEY `guild_id` (`guild_id`), + KEY `rank_id` (`rank_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_ranks` +-- + +CREATE TABLE `guild_ranks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild_id` int(11) NOT NULL COMMENT 'guild', + `name` varchar(255) NOT NULL COMMENT 'rank name', + `level` int(11) NOT NULL COMMENT 'rank level - leader, vice, member, maybe something else', + PRIMARY KEY (`id`), + KEY `guild_id` (`guild_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `guild_wars` +-- + +CREATE TABLE `guild_wars` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `guild1` int(11) NOT NULL DEFAULT '0', + `guild2` int(11) NOT NULL DEFAULT '0', + `name1` varchar(255) NOT NULL, + `name2` varchar(255) NOT NULL, + `status` tinyint(2) NOT NULL DEFAULT '0', + `started` bigint(15) NOT NULL DEFAULT '0', + `ended` bigint(15) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `guild1` (`guild1`), + KEY `guild2` (`guild2`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `houses` +-- + +CREATE TABLE `houses` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `owner` int(11) NOT NULL, + `paid` int(10) unsigned NOT NULL DEFAULT '0', + `warnings` int(11) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + `rent` int(11) NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `bid` int(11) NOT NULL DEFAULT '0', + `bid_end` int(11) NOT NULL DEFAULT '0', + `last_bid` int(11) NOT NULL DEFAULT '0', + `highest_bidder` int(11) NOT NULL DEFAULT '0', + `size` int(11) NOT NULL DEFAULT '0', + `beds` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `owner` (`owner`), + KEY `town_id` (`town_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=862 ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `house_lists` +-- + +CREATE TABLE `house_lists` ( + `house_id` int(11) NOT NULL, + `listid` int(11) NOT NULL, + `list` text NOT NULL, + KEY `house_id` (`house_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `ip_bans` +-- + +CREATE TABLE `ip_bans` ( + `ip` int(10) unsigned NOT NULL, + `reason` varchar(255) NOT NULL, + `banned_at` bigint(20) NOT NULL, + `expires_at` bigint(20) NOT NULL, + `banned_by` int(11) NOT NULL, + PRIMARY KEY (`ip`), + KEY `banned_by` (`banned_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `players` +-- + +CREATE TABLE `players` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `group_id` int(11) NOT NULL DEFAULT '1', + `account_id` int(11) NOT NULL DEFAULT '0', + `level` int(11) NOT NULL DEFAULT '1', + `vocation` int(11) NOT NULL DEFAULT '0', + `health` int(11) NOT NULL DEFAULT '150', + `healthmax` int(11) NOT NULL DEFAULT '150', + `experience` bigint(20) NOT NULL DEFAULT '0', + `lookbody` int(11) NOT NULL DEFAULT '0', + `lookfeet` int(11) NOT NULL DEFAULT '0', + `lookhead` int(11) NOT NULL DEFAULT '0', + `looklegs` int(11) NOT NULL DEFAULT '0', + `looktype` int(11) NOT NULL DEFAULT '136', + `maglevel` int(11) NOT NULL DEFAULT '0', + `mana` int(11) NOT NULL DEFAULT '0', + `manamax` int(11) NOT NULL DEFAULT '0', + `manaspent` int(11) unsigned NOT NULL DEFAULT '0', + `soul` int(10) unsigned NOT NULL DEFAULT '0', + `town_id` int(11) NOT NULL DEFAULT '0', + `posx` int(11) NOT NULL DEFAULT '0', + `posy` int(11) NOT NULL DEFAULT '0', + `posz` int(11) NOT NULL DEFAULT '0', + `conditions` blob NOT NULL, + `cap` int(11) NOT NULL DEFAULT '0', + `sex` int(11) NOT NULL DEFAULT '0', + `lastlogin` bigint(20) unsigned NOT NULL DEFAULT '0', + `lastip` int(10) unsigned NOT NULL DEFAULT '0', + `save` tinyint(1) NOT NULL DEFAULT '1', + `skull` tinyint(1) NOT NULL DEFAULT '0', + `skulltime` int(11) NOT NULL DEFAULT '0', + `lastlogout` bigint(20) unsigned NOT NULL DEFAULT '0', + `blessings` tinyint(2) NOT NULL DEFAULT '0', + `onlinetime` int(11) NOT NULL DEFAULT '0', + `deletion` bigint(15) NOT NULL DEFAULT '0', + `balance` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_fist` int(10) unsigned NOT NULL DEFAULT '10', + `skill_fist_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_club` int(10) unsigned NOT NULL DEFAULT '10', + `skill_club_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_sword` int(10) unsigned NOT NULL DEFAULT '10', + `skill_sword_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_axe` int(10) unsigned NOT NULL DEFAULT '10', + `skill_axe_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_dist` int(10) unsigned NOT NULL DEFAULT '10', + `skill_dist_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_shielding` int(10) unsigned NOT NULL DEFAULT '10', + `skill_shielding_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `skill_fishing` int(10) unsigned NOT NULL DEFAULT '10', + `skill_fishing_tries` bigint(20) unsigned NOT NULL DEFAULT '0', + `deleted` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `account_id` (`account_id`), + KEY `vocation` (`vocation`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ; + +-- +-- Dumping data for table `players` +-- + +INSERT INTO `players` (`id`, `name`, `group_id`, `account_id`, `level`, `vocation`, `health`, `healthmax`, `experience`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `maglevel`, `mana`, `manamax`, `manaspent`, `soul`, `town_id`, `posx`, `posy`, `posz`, `conditions`, `cap`, `sex`, `lastlogin`, `lastip`, `save`, `skull`, `skulltime`, `lastlogout`, `blessings`, `onlinetime`, `deletion`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries`, `deleted`) VALUES +(1, 'GM Nostalrius', 3, 1234567, 2, 0, 155, 155, 105, 106, 95, 78, 58, 75, 0, 5, 5, 0, 100, 10, 32316, 31942, 7, '', 405, 0, 1494975992, 16777343, 1, 0, 0, 1494976015, 0, 23, 0, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 10, 0, 0); + +-- +-- Triggers `players` +-- +DROP TRIGGER IF EXISTS `ondelete_players`; +DELIMITER // +CREATE TRIGGER `ondelete_players` BEFORE DELETE ON `players` + FOR EACH ROW BEGIN + UPDATE `houses` SET `owner` = 0 WHERE `owner` = OLD.`id`; +END +// +DELIMITER ; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `players_online` +-- + +CREATE TABLE `players_online` ( + `player_id` int(11) NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MEMORY DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_deaths` +-- + +CREATE TABLE `player_deaths` ( + `player_id` int(11) NOT NULL, + `time` bigint(20) unsigned NOT NULL DEFAULT '0', + `level` int(11) NOT NULL DEFAULT '1', + `killed_by` varchar(255) NOT NULL, + `is_player` tinyint(1) NOT NULL DEFAULT '1', + `mostdamage_by` varchar(100) NOT NULL, + `mostdamage_is_player` tinyint(1) NOT NULL DEFAULT '0', + `unjustified` tinyint(1) NOT NULL DEFAULT '0', + `mostdamage_unjustified` tinyint(1) NOT NULL DEFAULT '0', + KEY `player_id` (`player_id`), + KEY `killed_by` (`killed_by`), + KEY `mostdamage_by` (`mostdamage_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_depotitems` +-- + +CREATE TABLE `player_depotitems` ( + `player_id` int(11) NOT NULL, + `sid` int(11) NOT NULL COMMENT 'any given range eg 0-100 will be reserved for depot lockers and all > 100 will be then normal items inside depots', + `pid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL, + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + UNIQUE KEY `player_id_2` (`player_id`,`sid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_items` +-- + +CREATE TABLE `player_items` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `pid` int(11) NOT NULL DEFAULT '0', + `sid` int(11) NOT NULL DEFAULT '0', + `itemtype` smallint(6) NOT NULL DEFAULT '0', + `count` smallint(5) NOT NULL DEFAULT '0', + `attributes` blob NOT NULL, + KEY `player_id` (`player_id`), + KEY `sid` (`sid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `player_items` +-- + +INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES +(1, 3, 101, 2853, 1, ''), +(1, 4, 102, 3562, 1, ''), +(1, 5, 103, 2920, 1, ''), +(1, 6, 104, 3270, 1, ''), +(1, 101, 105, 3585, 1, 0x0f01); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_namelocks` +-- + +CREATE TABLE `player_namelocks` ( + `player_id` int(11) NOT NULL, + `reason` varchar(255) NOT NULL, + `namelocked_at` bigint(20) NOT NULL, + `namelocked_by` int(11) NOT NULL, + PRIMARY KEY (`player_id`), + KEY `namelocked_by` (`namelocked_by`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_spells` +-- + +CREATE TABLE `player_spells` ( + `player_id` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + KEY `player_id` (`player_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `player_storage` +-- + +CREATE TABLE `player_storage` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `key` int(10) unsigned NOT NULL DEFAULT '0', + `value` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`player_id`,`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `server_config` +-- + +CREATE TABLE `server_config` ( + `config` varchar(50) NOT NULL, + `value` varchar(256) NOT NULL DEFAULT '', + PRIMARY KEY (`config`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `server_config` +-- + +INSERT INTO `server_config` (`config`, `value`) VALUES +('motd_hash', '0'), +('motd_num', '0'), +('players_record', '0'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `tile_store` +-- + +CREATE TABLE `tile_store` ( + `house_id` int(11) NOT NULL, + `data` longblob NOT NULL, + KEY `house_id` (`house_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Constraints for dumped tables +-- + +-- +-- Constraints for table `account_bans` +-- +ALTER TABLE `account_bans` + ADD CONSTRAINT `account_bans_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `account_bans_ibfk_2` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `account_ban_history` +-- +ALTER TABLE `account_ban_history` + ADD CONSTRAINT `account_ban_history_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `account_ban_history_ibfk_2` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `account_viplist` +-- +ALTER TABLE `account_viplist` + ADD CONSTRAINT `account_viplist_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `account_viplist_ibfk_2` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guilds` +-- +ALTER TABLE `guilds` + ADD CONSTRAINT `guilds_ibfk_1` FOREIGN KEY (`ownerid`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guildwar_kills` +-- +ALTER TABLE `guildwar_kills` + ADD CONSTRAINT `guildwar_kills_ibfk_1` FOREIGN KEY (`warid`) REFERENCES `guild_wars` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guild_invites` +-- +ALTER TABLE `guild_invites` + ADD CONSTRAINT `guild_invites_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE, + ADD CONSTRAINT `guild_invites_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `guild_membership` +-- +ALTER TABLE `guild_membership` + ADD CONSTRAINT `guild_membership_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_2` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `guild_membership_ibfk_3` FOREIGN KEY (`rank_id`) REFERENCES `guild_ranks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `guild_ranks` +-- +ALTER TABLE `guild_ranks` + ADD CONSTRAINT `guild_ranks_ibfk_1` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `house_lists` +-- +ALTER TABLE `house_lists` + ADD CONSTRAINT `house_lists_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `ip_bans` +-- +ALTER TABLE `ip_bans` + ADD CONSTRAINT `ip_bans_ibfk_1` FOREIGN KEY (`banned_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `players` +-- +ALTER TABLE `players` + ADD CONSTRAINT `players_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_deaths` +-- +ALTER TABLE `player_deaths` + ADD CONSTRAINT `player_deaths_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_depotitems` +-- +ALTER TABLE `player_depotitems` + ADD CONSTRAINT `player_depotitems_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_items` +-- +ALTER TABLE `player_items` + ADD CONSTRAINT `player_items_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_namelocks` +-- +ALTER TABLE `player_namelocks` + ADD CONSTRAINT `player_namelocks_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `player_namelocks_ibfk_2` FOREIGN KEY (`namelocked_by`) REFERENCES `players` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- +-- Constraints for table `player_spells` +-- +ALTER TABLE `player_spells` + ADD CONSTRAINT `player_spells_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `player_storage` +-- +ALTER TABLE `player_storage` + ADD CONSTRAINT `player_storage_ibfk_1` FOREIGN KEY (`player_id`) REFERENCES `players` (`id`) ON DELETE CASCADE; + +-- +-- Constraints for table `tile_store` +-- +ALTER TABLE `tile_store` + ADD CONSTRAINT `tile_store_ibfk_1` FOREIGN KEY (`house_id`) REFERENCES `houses` (`id`) ON DELETE CASCADE; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..ee3ba35 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,71 @@ +set(tfs_SRC + ${CMAKE_CURRENT_LIST_DIR}/otpch.cpp + ${CMAKE_CURRENT_LIST_DIR}/actions.cpp + ${CMAKE_CURRENT_LIST_DIR}/ban.cpp + ${CMAKE_CURRENT_LIST_DIR}/baseevents.cpp + ${CMAKE_CURRENT_LIST_DIR}/bed.cpp + ${CMAKE_CURRENT_LIST_DIR}/behaviourdatabase.cpp + ${CMAKE_CURRENT_LIST_DIR}/chat.cpp + ${CMAKE_CURRENT_LIST_DIR}/combat.cpp + ${CMAKE_CURRENT_LIST_DIR}/commands.cpp + ${CMAKE_CURRENT_LIST_DIR}/condition.cpp + ${CMAKE_CURRENT_LIST_DIR}/configmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/connection.cpp + ${CMAKE_CURRENT_LIST_DIR}/container.cpp + ${CMAKE_CURRENT_LIST_DIR}/creature.cpp + ${CMAKE_CURRENT_LIST_DIR}/creatureevent.cpp + ${CMAKE_CURRENT_LIST_DIR}/cylinder.cpp + ${CMAKE_CURRENT_LIST_DIR}/database.cpp + ${CMAKE_CURRENT_LIST_DIR}/databasemanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/databasetasks.cpp + ${CMAKE_CURRENT_LIST_DIR}/depotlocker.cpp + ${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp + ${CMAKE_CURRENT_LIST_DIR}/game.cpp + ${CMAKE_CURRENT_LIST_DIR}/globalevent.cpp + ${CMAKE_CURRENT_LIST_DIR}/guild.cpp + ${CMAKE_CURRENT_LIST_DIR}/groups.cpp + ${CMAKE_CURRENT_LIST_DIR}/house.cpp + ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp + ${CMAKE_CURRENT_LIST_DIR}/ioguild.cpp + ${CMAKE_CURRENT_LIST_DIR}/iologindata.cpp + ${CMAKE_CURRENT_LIST_DIR}/iomap.cpp + ${CMAKE_CURRENT_LIST_DIR}/iomapserialize.cpp + ${CMAKE_CURRENT_LIST_DIR}/item.cpp + ${CMAKE_CURRENT_LIST_DIR}/items.cpp + ${CMAKE_CURRENT_LIST_DIR}/luascript.cpp + ${CMAKE_CURRENT_LIST_DIR}/mailbox.cpp + ${CMAKE_CURRENT_LIST_DIR}/map.cpp + ${CMAKE_CURRENT_LIST_DIR}/monster.cpp + ${CMAKE_CURRENT_LIST_DIR}/monsters.cpp + ${CMAKE_CURRENT_LIST_DIR}/movement.cpp + ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp + ${CMAKE_CURRENT_LIST_DIR}/npc.cpp + ${CMAKE_CURRENT_LIST_DIR}/otserv.cpp + ${CMAKE_CURRENT_LIST_DIR}/outputmessage.cpp + ${CMAKE_CURRENT_LIST_DIR}/party.cpp + ${CMAKE_CURRENT_LIST_DIR}/player.cpp + ${CMAKE_CURRENT_LIST_DIR}/position.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp + ${CMAKE_CURRENT_LIST_DIR}/raids.cpp + ${CMAKE_CURRENT_LIST_DIR}/rsa.cpp + ${CMAKE_CURRENT_LIST_DIR}/scheduler.cpp + ${CMAKE_CURRENT_LIST_DIR}/scriptmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/server.cpp + ${CMAKE_CURRENT_LIST_DIR}/spawn.cpp + ${CMAKE_CURRENT_LIST_DIR}/spells.cpp + ${CMAKE_CURRENT_LIST_DIR}/script.cpp + ${CMAKE_CURRENT_LIST_DIR}/talkaction.cpp + ${CMAKE_CURRENT_LIST_DIR}/tasks.cpp + ${CMAKE_CURRENT_LIST_DIR}/teleport.cpp + ${CMAKE_CURRENT_LIST_DIR}/thing.cpp + ${CMAKE_CURRENT_LIST_DIR}/tile.cpp + ${CMAKE_CURRENT_LIST_DIR}/tools.cpp + ${CMAKE_CURRENT_LIST_DIR}/vocation.cpp + ${CMAKE_CURRENT_LIST_DIR}/waitlist.cpp + ${CMAKE_CURRENT_LIST_DIR}/weapons.cpp + ${CMAKE_CURRENT_LIST_DIR}/wildcardtree.cpp +) + diff --git a/src/account.h b/src/account.h new file mode 100644 index 0000000..a7741f4 --- /dev/null +++ b/src/account.h @@ -0,0 +1,35 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F +#define FS_ACCOUNT_H_34817537BA2B4CB7B71AA562AFBB118F + +#include "enums.h" + +struct Account { + std::vector characters; + time_t lastDay = 0; + uint32_t id = 0; + uint16_t premiumDays = 0; + AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + + Account() = default; +}; + +#endif diff --git a/src/actions.cpp b/src/actions.cpp new file mode 100644 index 0000000..748bcab --- /dev/null +++ b/src/actions.cpp @@ -0,0 +1,425 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "actions.h" +#include "bed.h" +#include "configmanager.h" +#include "container.h" +#include "game.h" +#include "pugicast.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Actions* g_actions; +extern ConfigManager g_config; + +Actions::Actions() : + scriptInterface("Action Interface") +{ + scriptInterface.initState(); +} + +Actions::~Actions() +{ + clear(); +} + +inline void Actions::clearMap(ActionUseMap& map) +{ + // Filter out duplicates to avoid double-free + std::unordered_set set; + for (const auto& it : map) { + set.insert(it.second); + } + map.clear(); + + for (Action* action : set) { + delete action; + } +} + +void Actions::clear() +{ + clearMap(useItemMap); + clearMap(actionItemMap); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& Actions::getScriptInterface() +{ + return scriptInterface; +} + +std::string Actions::getScriptBaseName() const +{ + return "actions"; +} + +Event* Actions::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "action") != 0) { + return nullptr; + } + return new Action(&scriptInterface); +} + +bool Actions::registerEvent(Event* event, const pugi::xml_node& node) +{ + Action* action = static_cast(event); //event is guaranteed to be an Action + + pugi::xml_attribute attr; + if ((attr = node.attribute("itemid"))) { + uint16_t id = pugi::cast(attr.value()); + + auto result = useItemMap.emplace(id, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; + } + return result.second; + } else if ((attr = node.attribute("fromid"))) { + pugi::xml_attribute toIdAttribute = node.attribute("toid"); + if (!toIdAttribute) { + std::cout << "[Warning - Actions::registerEvent] Missing toid in fromid: " << attr.as_string() << std::endl; + return false; + } + + uint16_t fromId = pugi::cast(attr.value()); + uint16_t iterId = fromId; + uint16_t toId = pugi::cast(toIdAttribute.value()); + + auto result = useItemMap.emplace(iterId, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + } + + bool success = result.second; + while (++iterId <= toId) { + result = useItemMap.emplace(iterId, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << iterId << " in fromid: " << fromId << ", toid: " << toId << std::endl; + continue; + } + success = true; + } + return success; + } else if ((attr = node.attribute("actionid"))) { + uint16_t aid = pugi::cast(attr.value()); + + auto result = actionItemMap.emplace(aid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl; + } + return result.second; + } else if ((attr = node.attribute("fromaid"))) { + pugi::xml_attribute toAidAttribute = node.attribute("toaid"); + if (!toAidAttribute) { + std::cout << "[Warning - Actions::registerEvent] Missing toaid in fromaid: " << attr.as_string() << std::endl; + return false; + } + + uint16_t fromAid = pugi::cast(attr.value()); + uint16_t iterAid = fromAid; + uint16_t toAid = pugi::cast(toAidAttribute.value()); + + auto result = actionItemMap.emplace(iterAid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + } + + bool success = result.second; + while (++iterAid <= toAid) { + result = actionItemMap.emplace(iterAid, action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with action id: " << iterAid << " in fromaid: " << fromAid << ", toaid: " << toAid << std::endl; + continue; + } + success = true; + } + return success; + } + return false; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos) +{ + if (pos.x != 0xFFFF) { + const Position& playerPos = player->getPosition(); + if (playerPos.z != pos.z) { + return playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS; + } + + if (!Position::areInRange<1, 1>(playerPos, pos)) { + return RETURNVALUE_TOOFARAWAY; + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Actions::canUse(const Player* player, const Position& pos, const Item* item) +{ + Action* action = getAction(item); + if (action) { + return action->canExecuteAction(player, pos); + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor) +{ + if (toPos.x == 0xFFFF) { + return RETURNVALUE_NOERROR; + } + + const Position& creaturePos = creature->getPosition(); + if (checkFloor && creaturePos.z != toPos.z) { + return creaturePos.z > toPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS; + } + + if (!Position::areInRange<7, 5>(toPos, creaturePos)) { + return RETURNVALUE_TOOFARAWAY; + } + + if (checkLineOfSight && !g_game.canThrowObjectTo(creaturePos, toPos)) { + return RETURNVALUE_CANNOTTHROW; + } + + return RETURNVALUE_NOERROR; +} + +Action* Actions::getAction(const Item* item) +{ + if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { + auto it = actionItemMap.find(item->getActionId()); + if (it != actionItemMap.end()) { + return it->second; + } + } + + auto it = useItemMap.find(item->getID()); + if (it != useItemMap.end()) { + return it->second; + } + + //rune items + return g_spells->getRuneSpell(item->getID()); +} + +ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item) +{ + if (Door* door = item->getDoor()) { + if (!door->canUse(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } + + Action* action = getAction(item); + if (action) { + if (action->isScripted()) { + if (action->executeUse(player, item, pos, nullptr, pos)) { + return RETURNVALUE_NOERROR; + } + + if (item->isRemoved()) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } + } + + if (BedItem* bed = item->getBed()) { + if (!bed->canUse(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + + if (bed->trySleep(player)) { + player->setBedItem(bed); + if (!bed->sleep(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + } + + return RETURNVALUE_NOERROR; + } + + if (Container* container = item->getContainer()) { + if (!item->isChestQuest()) { + Container* openContainer; + + //depot container + if (DepotLocker* depot = container->getDepotLocker()) { + DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true); + myDepotLocker->setParent(depot->getParent()->getTile()); + openContainer = myDepotLocker; + } else { + openContainer = container; + } + + //open/close container + int32_t oldContainerId = player->getContainerID(openContainer); + if (oldContainerId != -1) { + player->onCloseContainer(openContainer); + player->closeContainer(oldContainerId); + } else { + player->addContainer(index, openContainer); + player->onSendContainer(openContainer); + } + + return RETURNVALUE_NOERROR; + } + } + + const ItemType& it = Item::items[item->getID()]; + if (it.canReadText) { + if (it.canWriteText) { + player->setWriteItem(item, it.maxTextLen); + player->sendTextWindow(item, it.maxTextLen, true); + } else { + player->setWriteItem(nullptr); + player->sendTextWindow(item, 0, false); + } + + return RETURNVALUE_NOERROR; + } else if (it.changeUse) { + if (it.transformToOnUse) { + g_game.transformItem(item, it.transformToOnUse); + g_game.startDecay(item); + return RETURNVALUE_NOERROR; + } + } + + return RETURNVALUE_CANNOTUSETHISOBJECT; +} + +bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* item) +{ + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + ReturnValue ret = internalUseItem(player, pos, index, item); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + return true; +} + +bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& toPos, + uint8_t toStackPos, Item* item, Creature* creature/* = nullptr*/) +{ + player->setNextAction(OTSYS_TIME() + g_config.getNumber(ConfigManager::EX_ACTIONS_DELAY_INTERVAL)); + player->stopWalk(); + + Action* action = getAction(item); + if (!action) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return false; + } + + ReturnValue ret = action->canExecuteAction(player, toPos); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return false; + } + + if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos)) { + if (!action->hasOwnErrorHandler()) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + } + return false; + } + return true; +} + +Action::Action(LuaScriptInterface* interface) : + Event(interface), allowFarUse(false), checkFloor(true), checkLineOfSight(true) {} + +Action::Action(const Action* copy) : + Event(copy), allowFarUse(copy->allowFarUse), checkFloor(copy->checkFloor), checkLineOfSight(copy->checkLineOfSight) {} + +bool Action::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute allowFarUseAttr = node.attribute("allowfaruse"); + if (allowFarUseAttr) { + setAllowFarUse(allowFarUseAttr.as_bool()); + } + + pugi::xml_attribute blockWallsAttr = node.attribute("blockwalls"); + if (blockWallsAttr) { + setCheckLineOfSight(blockWallsAttr.as_bool()); + } + + pugi::xml_attribute checkFloorAttr = node.attribute("checkfloor"); + if (checkFloorAttr) { + setCheckFloor(checkFloorAttr.as_bool()); + } + + return true; +} + +std::string Action::getScriptEventName() const +{ + return "onUse"; +} + +ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos) +{ + if (!getAllowFarUse()) { + return g_actions->canUse(player, toPos); + } else { + return g_actions->canUseFar(player, toPos, getCheckLineOfSight(), getCheckFloor()); + } +} + +Thing* Action::getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const +{ + if (targetCreature) { + return targetCreature; + } + return g_game.internalGetThing(player, toPosition, toStackPos, 0, STACKPOS_USETARGET); +} + +bool Action::executeUse(Player* player, Item* item, const Position& fromPos, Thing* target, const Position& toPos) +{ + //onUse(player, item, fromPosition, target, toPosition) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Action::executeUse] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushPosition(L, fromPos); + + LuaScriptInterface::pushThing(L, target); + LuaScriptInterface::pushPosition(L, toPos); + + return scriptInterface->callFunction(5); +} diff --git a/src/actions.h b/src/actions.h new file mode 100644 index 0000000..02c4924 --- /dev/null +++ b/src/actions.h @@ -0,0 +1,111 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 +#define FS_ACTIONS_H_87F60C5F587E4B84948F304A6451E6E6 + +#include "baseevents.h" +#include "enums.h" +#include "luascript.h" + +class Action : public Event +{ + public: + explicit Action(const Action* copy); + explicit Action(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + + //scripting + virtual bool executeUse(Player* player, Item* item, const Position& fromPosition, + Thing* target, const Position& toPosition); + // + + bool getAllowFarUse() const { + return allowFarUse; + } + void setAllowFarUse(bool v) { + allowFarUse = v; + } + + bool getCheckLineOfSight() const { + return checkLineOfSight; + } + void setCheckLineOfSight(bool v) { + checkLineOfSight = v; + } + + bool getCheckFloor() const { + return checkFloor; + } + void setCheckFloor(bool v) { + checkFloor = v; + } + + virtual ReturnValue canExecuteAction(const Player* player, const Position& toPos); + virtual bool hasOwnErrorHandler() { + return false; + } + virtual Thing* getTarget(Player* player, Creature* targetCreature, const Position& toPosition, uint8_t toStackPos) const; + + protected: + std::string getScriptEventName() const override; + + bool allowFarUse; + bool checkFloor; + bool checkLineOfSight; +}; + +class Actions final : public BaseEvents +{ + public: + Actions(); + ~Actions(); + + // non-copyable + Actions(const Actions&) = delete; + Actions& operator=(const Actions&) = delete; + + bool useItem(Player* player, const Position& pos, uint8_t index, Item* item); + bool useItemEx(Player* player, const Position& fromPos, const Position& toPos, uint8_t toStackPos, Item* item, Creature* creature = nullptr); + + ReturnValue canUse(const Player* player, const Position& pos); + ReturnValue canUse(const Player* player, const Position& pos, const Item* item); + ReturnValue canUseFar(const Creature* creature, const Position& toPos, bool checkLineOfSight, bool checkFloor); + + protected: + ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item); + + void clear() final; + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + typedef std::map ActionUseMap; + ActionUseMap useItemMap; + ActionUseMap actionItemMap; + + Action* getAction(const Item* item); + void clearMap(ActionUseMap& map); + + LuaScriptInterface scriptInterface; +}; + +#endif diff --git a/src/ban.cpp b/src/ban.cpp new file mode 100644 index 0000000..a531ea9 --- /dev/null +++ b/src/ban.cpp @@ -0,0 +1,127 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "ban.h" +#include "database.h" +#include "databasetasks.h" +#include "tools.h" + +bool Ban::acceptConnection(uint32_t clientip) +{ + std::lock_guard lockClass(lock); + + uint64_t currentTime = OTSYS_TIME(); + + auto it = ipConnectMap.find(clientip); + if (it == ipConnectMap.end()) { + ipConnectMap.emplace(clientip, ConnectBlock(currentTime, 0, 1)); + return true; + } + + ConnectBlock& connectBlock = it->second; + if (connectBlock.blockTime > currentTime) { + connectBlock.blockTime += 250; + return false; + } + + int64_t timeDiff = currentTime - connectBlock.lastAttempt; + connectBlock.lastAttempt = currentTime; + if (timeDiff <= 5000) { + if (++connectBlock.count > 5) { + connectBlock.count = 0; + if (timeDiff <= 500) { + connectBlock.blockTime = currentTime + 3000; + return false; + } + } + } else { + connectBlock.count = 1; + } + return true; +} + +bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `reason`, `expires_at`, `banned_at`, `banned_by`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `account_bans` WHERE `account_id` = " << accountId; + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + int64_t expiresAt = result->getNumber("expires_at"); + if (expiresAt != 0 && time(nullptr) > expiresAt) { + // Move the ban to history if it has expired + query.str(std::string()); + query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db->escapeString(result->getString("reason")) << ',' << result->getNumber("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; + g_databaseTasks.addTask(query.str()); + + query.str(std::string()); + query << "DELETE FROM `account_bans` WHERE `account_id` = " << accountId; + g_databaseTasks.addTask(query.str()); + return false; + } + + banInfo.expiresAt = expiresAt; + banInfo.reason = result->getString("reason"); + banInfo.bannedBy = result->getString("name"); + return true; +} + +bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo) +{ + if (clientip == 0) { + return false; + } + + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `reason`, `expires_at`, (SELECT `name` FROM `players` WHERE `id` = `banned_by`) AS `name` FROM `ip_bans` WHERE `ip` = " << clientip; + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + int64_t expiresAt = result->getNumber("expires_at"); + if (expiresAt != 0 && time(nullptr) > expiresAt) { + query.str(std::string()); + query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientip; + g_databaseTasks.addTask(query.str()); + return false; + } + + banInfo.expiresAt = expiresAt; + banInfo.reason = result->getString("reason"); + banInfo.bannedBy = result->getString("name"); + return true; +} + +bool IOBan::isPlayerNamelocked(uint32_t playerId) +{ + std::ostringstream query; + query << "SELECT 1 FROM `player_namelocks` WHERE `player_id` = " << playerId; + return Database::getInstance()->storeQuery(query.str()).get() != nullptr; +} diff --git a/src/ban.h b/src/ban.h new file mode 100644 index 0000000..0ca2360 --- /dev/null +++ b/src/ban.h @@ -0,0 +1,58 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 +#define FS_BAN_H_CADB975222D745F0BDA12D982F1006E3 + +struct BanInfo { + std::string bannedBy; + std::string reason; + time_t expiresAt; +}; + +struct ConnectBlock { + constexpr ConnectBlock(uint64_t lastAttempt, uint64_t blockTime, uint32_t count) : + lastAttempt(lastAttempt), blockTime(blockTime), count(count) {} + + uint64_t lastAttempt; + uint64_t blockTime; + uint32_t count; +}; + +typedef std::map IpConnectMap; + +class Ban +{ + public: + bool acceptConnection(uint32_t clientip); + + protected: + IpConnectMap ipConnectMap; + std::recursive_mutex lock; +}; + +class IOBan +{ + public: + static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); + static bool isIpBanned(uint32_t ip, BanInfo& banInfo); + static bool isPlayerNamelocked(uint32_t playerId); +}; + +#endif diff --git a/src/baseevents.cpp b/src/baseevents.cpp new file mode 100644 index 0000000..f8191fc --- /dev/null +++ b/src/baseevents.cpp @@ -0,0 +1,165 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "baseevents.h" + +#include "pugicast.h" +#include "tools.h" + +extern LuaEnvironment g_luaEnvironment; + +bool BaseEvents::loadFromXml() +{ + if (loaded) { + std::cout << "[Error - BaseEvents::loadFromXml] It's already loaded." << std::endl; + return false; + } + + std::string scriptsName = getScriptBaseName(); + std::string basePath = "data/" + scriptsName + "/"; + if (getScriptInterface().loadFile(basePath + "lib/" + scriptsName + ".lua") == -1) { + std::cout << "[Warning - BaseEvents::loadFromXml] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + std::string filename = basePath + scriptsName + ".xml"; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - BaseEvents::loadFromXml", filename, result); + return false; + } + + loaded = true; + + for (auto node : doc.child(scriptsName.c_str()).children()) { + Event* event = getEvent(node.name()); + if (!event) { + continue; + } + + if (!event->configureEvent(node)) { + std::cout << "[Warning - BaseEvents::loadFromXml] Failed to configure event" << std::endl; + delete event; + continue; + } + + bool success; + + pugi::xml_attribute scriptAttribute = node.attribute("script"); + if (scriptAttribute) { + std::string scriptFile = "scripts/" + std::string(scriptAttribute.as_string()); + success = event->checkScript(basePath, scriptsName, scriptFile) && event->loadScript(basePath + scriptFile); + } else { + success = event->loadFunction(node.attribute("function")); + } + + if (!success || !registerEvent(event, node)) { + delete event; + } + } + return true; +} + +bool BaseEvents::reload() +{ + loaded = false; + clear(); + return loadFromXml(); +} + +Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {} + +Event::Event(const Event* copy) : + scripted(copy->scripted), scriptId(copy->scriptId), scriptInterface(copy->scriptInterface) {} + +bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const +{ + LuaScriptInterface* testInterface = g_luaEnvironment.getTestInterface(); + testInterface->reInitState(); + + if (testInterface->loadFile(std::string(basePath + "lib/" + scriptsName + ".lua")) == -1) { + std::cout << "[Warning - Event::checkScript] Can not load " << scriptsName << " lib/" << scriptsName << ".lua" << std::endl; + } + + if (scriptId != 0) { + std::cout << "[Failure - Event::checkScript] scriptid = " << scriptId << std::endl; + return false; + } + + if (testInterface->loadFile(basePath + scriptFile) == -1) { + std::cout << "[Warning - Event::checkScript] Can not load script: " << scriptFile << std::endl; + std::cout << testInterface->getLastLuaError() << std::endl; + return false; + } + + int32_t id = testInterface->getEvent(getScriptEventName()); + if (id == -1) { + std::cout << "[Warning - Event::checkScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + return true; +} + +bool Event::loadScript(const std::string& scriptFile) +{ + if (!scriptInterface || scriptId != 0) { + std::cout << "Failure: [Event::loadScript] scriptInterface == nullptr. scriptid = " << scriptId << std::endl; + return false; + } + + if (scriptInterface->loadFile(scriptFile) == -1) { + std::cout << "[Warning - Event::loadScript] Can not load script. " << scriptFile << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + return false; + } + + int32_t id = scriptInterface->getEvent(getScriptEventName()); + if (id == -1) { + std::cout << "[Warning - Event::loadScript] Event " << getScriptEventName() << " not found. " << scriptFile << std::endl; + return false; + } + + scripted = true; + scriptId = id; + return true; +} + +bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& name) +{ + if (!interface) { + std::cout << "Failure: [CallBack::loadCallBack] scriptInterface == nullptr" << std::endl; + return false; + } + + scriptInterface = interface; + + int32_t id = scriptInterface->getEvent(name.c_str()); + if (id == -1) { + std::cout << "[Warning - CallBack::loadCallBack] Event " << name << " not found." << std::endl; + return false; + } + + callbackName = name; + scriptId = id; + loaded = true; + return true; +} diff --git a/src/baseevents.h b/src/baseevents.h new file mode 100644 index 0000000..3529a6a --- /dev/null +++ b/src/baseevents.h @@ -0,0 +1,90 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A +#define FS_BASEEVENTS_H_9994E32C91CE4D95912A5FDD1F41884A + +#include "luascript.h" + +class Event +{ + public: + explicit Event(LuaScriptInterface* interface); + explicit Event(const Event* copy); + virtual ~Event() = default; + + virtual bool configureEvent(const pugi::xml_node& node) = 0; + + bool checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const; + bool loadScript(const std::string& scriptFile); + virtual bool loadFunction(const pugi::xml_attribute&) { + return false; + } + + bool isScripted() const { + return scripted; + } + + protected: + virtual std::string getScriptEventName() const = 0; + + bool scripted = false; + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; +}; + +class BaseEvents +{ + public: + constexpr BaseEvents() = default; + virtual ~BaseEvents() = default; + + bool loadFromXml(); + bool reload(); + bool isLoaded() const { + return loaded; + } + + protected: + virtual LuaScriptInterface& getScriptInterface() = 0; + virtual std::string getScriptBaseName() const = 0; + virtual Event* getEvent(const std::string& nodeName) = 0; + virtual bool registerEvent(Event* event, const pugi::xml_node& node) = 0; + virtual void clear() = 0; + + bool loaded = false; +}; + +class CallBack +{ + public: + CallBack() = default; + + bool loadCallBack(LuaScriptInterface* interface, const std::string& name); + + protected: + int32_t scriptId = 0; + LuaScriptInterface* scriptInterface = nullptr; + + bool loaded = false; + + std::string callbackName; +}; + +#endif diff --git a/src/bed.cpp b/src/bed.cpp new file mode 100644 index 0000000..a2f55aa --- /dev/null +++ b/src/bed.cpp @@ -0,0 +1,277 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "bed.h" +#include "game.h" +#include "iologindata.h" +#include "scheduler.h" + +extern Game g_game; + +BedItem::BedItem(uint16_t id) : Item(id) +{ + internalRemoveSleeper(); +} + +Attr_ReadValue BedItem::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_SLEEPERGUID: { + uint32_t guid; + if (!propStream.read(guid)) { + return ATTR_READ_ERROR; + } + + if (guid != 0) { + std::string name = IOLoginData::getNameByGuid(guid); + if (!name.empty()) { + setSpecialDescription(name + " is sleeping there."); + g_game.setBedSleeper(this, guid); + sleeperGUID = guid; + } + } + return ATTR_READ_CONTINUE; + } + + case ATTR_SLEEPSTART: { + uint32_t sleep_start; + if (!propStream.read(sleep_start)) { + return ATTR_READ_ERROR; + } + + sleepStart = static_cast(sleep_start); + return ATTR_READ_CONTINUE; + } + + default: + break; + } + return Item::readAttr(attr, propStream); +} + +void BedItem::serializeAttr(PropWriteStream& propWriteStream) const +{ + if (sleeperGUID != 0) { + propWriteStream.write(ATTR_SLEEPERGUID); + propWriteStream.write(sleeperGUID); + } + + if (sleepStart != 0) { + propWriteStream.write(ATTR_SLEEPSTART); + // FIXME: should be stored as 64-bit, but we need to retain backwards compatibility + propWriteStream.write(static_cast(sleepStart)); + } +} + +BedItem* BedItem::getNextBedItem() const +{ + Direction dir = Item::items[id].bedPartnerDir; + Position targetPos = getNextPosition(dir, getPosition()); + + Tile* tile = g_game.map.getTile(targetPos); + if (!tile) { + return nullptr; + } + return tile->getBedItem(); +} + +bool BedItem::canUse(Player* player) +{ + if (!player || !house || !player->isPremium()) { + return false; + } + + if (sleeperGUID == 0) { + return true; + } + + if (house->getHouseAccessLevel(player) == HOUSE_OWNER) { + return true; + } + + Player sleeper(nullptr); + if (!IOLoginData::loadPlayerById(&sleeper, sleeperGUID)) { + return false; + } + + if (house->getHouseAccessLevel(&sleeper) > house->getHouseAccessLevel(player)) { + return false; + } + return true; +} + +bool BedItem::trySleep(Player* player) +{ + if (!house || player->isRemoved()) { + return false; + } + + if (sleeperGUID != 0) { + if (Item::items[id].transformToFree != 0 && house->getOwner() == player->getGUID()) { + wakeUp(nullptr); + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + return true; +} + +bool BedItem::sleep(Player* player) +{ + if (!house) { + return false; + } + + if (sleeperGUID != 0) { + return false; + } + + BedItem* nextBedItem = getNextBedItem(); + + internalSetSleeper(player); + + if (nextBedItem) { + nextBedItem->internalSetSleeper(player); + } + + // update the bedSleepersMap + g_game.setBedSleeper(this, player->getGUID()); + + // make the player walk onto the bed + g_game.map.moveCreature(*player, *getTile()); + + // display poff effect + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + + // kick player after he sees himself walk onto the bed and it change id + uint32_t playerId = player->getID(); + g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&Game::kickPlayer, &g_game, playerId, false))); + + // change self and partner's appearance + updateAppearance(player); + + if (nextBedItem) { + nextBedItem->updateAppearance(player); + } + + return true; +} + +void BedItem::wakeUp(Player* player) +{ + if (!house) { + return; + } + + if (sleeperGUID != 0) { + if (!player) { + Player regenPlayer(nullptr); + if (IOLoginData::loadPlayerById(®enPlayer, sleeperGUID)) { + regeneratePlayer(®enPlayer); + IOLoginData::savePlayer(®enPlayer); + } + } else { + regeneratePlayer(player); + g_game.addCreatureHealth(player); + } + } + + // update the bedSleepersMap + g_game.removeBedSleeper(sleeperGUID); + + BedItem* nextBedItem = getNextBedItem(); + + // unset sleep info + internalRemoveSleeper(); + + if (nextBedItem) { + nextBedItem->internalRemoveSleeper(); + } + + // change self and partner's appearance + updateAppearance(nullptr); + + if (nextBedItem) { + nextBedItem->updateAppearance(nullptr); + } +} + +void BedItem::regeneratePlayer(Player* player) const +{ + const uint32_t sleptTime = time(nullptr) - sleepStart; + + Condition* condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + if (condition) { + uint32_t regen; + if (condition->getTicks() != -1) { + regen = std::min((condition->getTicks() / 1000), sleptTime) / 30; + const int32_t newRegenTicks = condition->getTicks() - (regen * 30000); + if (newRegenTicks <= 0) { + player->removeCondition(condition); + } else { + condition->setTicks(newRegenTicks); + } + } else { + regen = sleptTime / 30; + } + + player->changeHealth(regen, false); + player->changeMana(regen); + } + + const int32_t soulRegen = sleptTime / (60 * 15); + player->changeSoul(soulRegen); +} + +void BedItem::updateAppearance(const Player* player) +{ + const ItemType& it = Item::items[id]; + if (it.type == ITEM_TYPE_BED) { + if (player && it.transformToOnUse != 0) { + const ItemType& newType = Item::items[it.transformToOnUse]; + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToOnUse); + } + } else if (it.transformToFree != 0) { + const ItemType& newType = Item::items[it.transformToFree]; + if (newType.type == ITEM_TYPE_BED) { + g_game.transformItem(this, it.transformToFree); + } + } + } +} + +void BedItem::internalSetSleeper(const Player* player) +{ + std::string desc_str = player->getName() + " is sleeping there."; + + sleeperGUID = player->getGUID(); + sleepStart = time(nullptr); + setSpecialDescription(desc_str); +} + +void BedItem::internalRemoveSleeper() +{ + sleeperGUID = 0; + sleepStart = 0; + setSpecialDescription("Nobody is sleeping there."); +} diff --git a/src/bed.h b/src/bed.h new file mode 100644 index 0000000..b45486d --- /dev/null +++ b/src/bed.h @@ -0,0 +1,77 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_BED_H_84DE19758D424C6C9789189231946BFF +#define FS_BED_H_84DE19758D424C6C9789189231946BFF + +#include "item.h" + +class House; +class Player; + +class BedItem final : public Item +{ + public: + explicit BedItem(uint16_t id); + + BedItem* getBed() final { + return this; + } + const BedItem* getBed() const final { + return this; + } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; + + bool canRemove() const final { + return house == nullptr; + } + + uint32_t getSleeper() const { + return sleeperGUID; + } + + House* getHouse() const { + return house; + } + void setHouse(House* h) { + house = h; + } + + bool canUse(Player* player); + + bool trySleep(Player* player); + bool sleep(Player* player); + void wakeUp(Player* player); + + BedItem* getNextBedItem() const; + + protected: + void updateAppearance(const Player* player); + void regeneratePlayer(Player* player) const; + void internalSetSleeper(const Player* player); + void internalRemoveSleeper(); + + House* house = nullptr; + uint64_t sleepStart; + uint32_t sleeperGUID; +}; + +#endif diff --git a/src/behaviourdatabase.cpp b/src/behaviourdatabase.cpp new file mode 100644 index 0000000..7ddc588 --- /dev/null +++ b/src/behaviourdatabase.cpp @@ -0,0 +1,1301 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2017 Alejandro Mujica +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "otpch.h" + +#include "behaviourdatabase.h" +#include "npc.h" +#include "player.h" +#include "game.h" +#include "spells.h" +#include "monster.h" +#include "scheduler.h" + +extern Game g_game; +extern Monsters g_monsters; +extern Spells* g_spells; + +BehaviourDatabase::BehaviourDatabase(Npc * _npc) : npc(_npc) { + topic = 0; + data = -1; + type = 0; + price = 0; + amount = 0; + delay = 1000; +} + +BehaviourDatabase::~BehaviourDatabase() { + for (NpcBehaviour* behaviour : behaviourEntries) { + delete behaviour; + } +} + +bool BehaviourDatabase::loadDatabase(ScriptReader& script) +{ + script.readSymbol('{'); + script.nextToken(); + while (true) { + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token == SPECIAL && script.getSpecial() == '}') { + break; + } + + if (!loadBehaviour(script)) { + return false; + } + } + + return true; +} + +bool BehaviourDatabase::loadBehaviour(ScriptReader& script) +{ + NpcBehaviour* behaviour = new NpcBehaviour(); + + if (!loadConditions(script, behaviour)) { + return false; + } + + if (script.Token != SPECIAL || script.getSpecial() != 'I') { + script.error("'->' expected"); + delete behaviour; + return false; + } + + script.nextToken(); + if (!loadActions(script, behaviour)) { + delete behaviour; + return false; + } + + // set this behaviour priority to condition size + behaviour->priority += behaviour->conditions.size(); + + if (priorityBehaviour) { + priorityBehaviour->priority += behaviour->priority + 1; + priorityBehaviour = nullptr; + } + + // order it correctly + auto it = std::lower_bound(behaviourEntries.begin(), behaviourEntries.end(), behaviour, compareBehaviour); + behaviourEntries.insert(it, behaviour); + + // set previous behaviour (*) functionality + previousBehaviour = behaviour; + return true; +} + +bool BehaviourDatabase::loadConditions(ScriptReader& script, NpcBehaviour* behaviour) +{ + while (true) { + std::unique_ptr condition(new NpcBehaviourCondition); + + bool searchTerm = false; + if (script.Token == IDENTIFIER) { + std::string identifier = script.getIdentifier(); + if (identifier == "address") { + condition->situation = SITUATION_ADDRESS; + behaviour->situation = SITUATION_ADDRESS; + searchTerm = true; + } else if (identifier == "busy") { + condition->situation = SITUATION_BUSY; + behaviour->situation = SITUATION_BUSY; + searchTerm = true; + } else if (identifier == "vanish") { + condition->situation = SITUATION_VANISH; + behaviour->situation = SITUATION_VANISH; + searchTerm = true; + } else if (identifier == "sorcerer") { + condition->type = BEHAVIOUR_TYPE_SORCERER; + searchTerm = true; + } else if (identifier == "knight") { + condition->type = BEHAVIOUR_TYPE_KNIGHT; + searchTerm = true; + } else if (identifier == "paladin") { + condition->type = BEHAVIOUR_TYPE_PALADIN; + searchTerm = true; + } else if (identifier == "druid") { + condition->type = BEHAVIOUR_TYPE_DRUID; + searchTerm = true; + } else if (identifier == "premium") { + condition->type = BEHAVIOUR_TYPE_ISPREMIUM; + searchTerm = true; + } else if (identifier == "pvpenforced") { + condition->type = BEHAVIOUR_TYPE_PVPENFORCED; + searchTerm = true; + } else if (identifier == "female") { + condition->type = BEHAVIOUR_TYPE_FEMALE; + searchTerm = true; + } else if (identifier == "male") { + condition->type = BEHAVIOUR_TYPE_MALE; + searchTerm = true; + } else if (identifier == "pzblock") { + condition->type = BEHAVIOUR_TYPE_PZLOCKED; + searchTerm = true; + } else if (identifier == "promoted") { + condition->type = BEHAVIOUR_TYPE_PROMOTED; + searchTerm = true; + } + } else if (script.Token == STRING) { + const std::string keyString = asLowerCaseString(script.getString()); + condition->setCondition(BEHAVIOUR_TYPE_STRING, 0, keyString); + behaviour->priority += keyString.length(); + + searchTerm = true; + } else if (script.Token == SPECIAL) { + if (script.getSpecial() == '!') { + condition->setCondition(BEHAVIOUR_TYPE_NOP, 0, ""); + searchTerm = true; + + // set this one for behaviour + priorityBehaviour = behaviour; + } else if (script.getSpecial() == '%') { + condition->setCondition(BEHAVIOUR_TYPE_MESSAGE_COUNT, script.readNumber(), ""); + searchTerm = true; + } else if (script.getSpecial() == ',') { + script.nextToken(); + continue; + } else { + break; + } + } + + // relational operation search + if (!searchTerm) { + condition->type = BEHAVIOUR_TYPE_OPERATION; + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + + // relational operators + if (script.Token != SPECIAL) { + script.error("relational operator expected"); + delete nextNode; + return false; + } + + NpcBehaviourOperator_t operatorType; + switch (script.getSpecial()) { + case '<': + operatorType = BEHAVIOUR_OPERATOR_LESSER_THAN; + break; + case '=': + operatorType = BEHAVIOUR_OPERATOR_EQUALS; + break; + case '>': + operatorType = BEHAVIOUR_OPERATOR_GREATER_THAN; + break; + case 'G': + operatorType = BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS; + break; + case 'N': + operatorType = BEHAVIOUR_OPERATOR_NOT_EQUALS; + break; + case 'L': + operatorType = BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS; + break; + default: + script.error("relational operator expected"); + delete nextNode; + return false; + } + + script.nextToken(); + headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = operatorType; + headNode->left = nextNode; + nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + headNode->right = nextNode; + + condition->expression = headNode; + } else { + script.nextToken(); + } + + behaviour->conditions.push_back(condition.release()); + } + + return true; +} + +bool BehaviourDatabase::loadActions(ScriptReader& script, NpcBehaviour* behaviour) +{ + while (true) { + std::unique_ptr action(new NpcBehaviourAction); + NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE; + + if (script.Token == STRING) { + action->type = BEHAVIOUR_TYPE_STRING; + action->string = script.getString(); + } else if (script.Token == IDENTIFIER) { + std::string identifier = script.getIdentifier(); + if (identifier == "idle") { + action->type = BEHAVIOUR_TYPE_IDLE; + } else if (identifier == "nop") { + action->type = BEHAVIOUR_TYPE_NOP; + } else if (identifier == "queue") { + action->type = BEHAVIOUR_TYPE_QUEUE; + } else if (identifier == "createmoney") { + action->type = BEHAVIOUR_TYPE_CREATEMONEY; + } else if (identifier == "deletemoney") { + action->type = BEHAVIOUR_TYPE_DELETEMONEY; + } else if (identifier == "promote") { + action->type = BEHAVIOUR_TYPE_PROMOTE; + } else if (identifier == "topic") { + action->type = BEHAVIOUR_TYPE_TOPIC; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "price") { + action->type = BEHAVIOUR_TYPE_PRICE; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "amount") { + action->type = BEHAVIOUR_TYPE_AMOUNT; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "data") { + action->type = BEHAVIOUR_TYPE_DATA; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "type") { + action->type = BEHAVIOUR_TYPE_ITEM; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "string") { + action->type = BEHAVIOUR_TYPE_TEXT; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "hp") { + action->type = BEHAVIOUR_TYPE_HEALTH; + searchType = BEHAVIOUR_PARAMETER_ASSIGN; + } else if (identifier == "withdraw") { + action->type = BEHAVIOUR_TYPE_WITHDRAW; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "deposit") { + action->type = BEHAVIOUR_TYPE_DEPOSIT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "bless") { + action->type = BEHAVIOUR_TYPE_BLESS; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "effectme") { + action->type = BEHAVIOUR_TYPE_EFFECTME; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "effectopp") { + action->type = BEHAVIOUR_TYPE_EFFECTOPP; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "create") { + action->type = BEHAVIOUR_TYPE_CREATE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "delete") { + action->type = BEHAVIOUR_TYPE_DELETE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "teachspell") { + action->type = BEHAVIOUR_TYPE_TEACHSPELL; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "town") { + action->type = BEHAVIOUR_TYPE_TOWN; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "profession") { + action->type = BEHAVIOUR_TYPE_PROFESSION; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "experience") { + action->type = BEHAVIOUR_TYPE_EXPERIENCE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "summon") { + action->type = BEHAVIOUR_TYPE_SUMMON; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "burning") { + action->type = BEHAVIOUR_TYPE_BURNING; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "setquestvalue") { + action->type = BEHAVIOUR_TYPE_QUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "poison") { + action->type = BEHAVIOUR_TYPE_POISON; + searchType = BEHAVIOUR_PARAMETER_TWO; + } else if (identifier == "teleport") { + action->type = BEHAVIOUR_TYPE_TELEPORT; + searchType = BEHAVIOUR_PARAMETER_THREE; + } else if (identifier == "createcontainer") { + action->type = BEHAVIOUR_TYPE_CREATECONTAINER; + searchType = BEHAVIOUR_PARAMETER_THREE; + } else { + script.error("illegal action term"); + return false; + } + } else if (script.Token == SPECIAL) { + if (script.getSpecial() == '*') { + if (previousBehaviour == nullptr) { + script.error("no previous pattern"); + return false; + } + + for (NpcBehaviourAction* actionCopy : previousBehaviour->actions) { + behaviour->actions.push_back(actionCopy->clone()); + } + script.nextToken(); + return true; + } + } + + if (searchType == BEHAVIOUR_PARAMETER_ASSIGN) { + script.readSymbol('='); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + } else if (searchType == BEHAVIOUR_PARAMETER_ONE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else if (searchType == BEHAVIOUR_PARAMETER_TWO) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression2 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else if (searchType == BEHAVIOUR_PARAMETER_THREE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* headNode = readValue(script); + NpcBehaviourNode* nextNode = readFactor(script, headNode); + action->expression = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression2 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + return false; + } + script.nextToken(); + headNode = readValue(script); + nextNode = readFactor(script, headNode); + action->expression3 = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + return false; + } + script.nextToken(); + } else { + script.nextToken(); + } + + behaviour->actions.push_back(action.release()); + + if (script.Token == SPECIAL) { + if (script.getSpecial() == ',') { + script.nextToken(); + continue; + } + } + + break; + } + + return true; +} + +NpcBehaviourNode* BehaviourDatabase::readValue(ScriptReader& script) +{ + if (script.Token == NUMBER) { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_NUMBER; + node->number = script.getNumber(); + script.nextToken(); + return node; + } + + if (script.Token == STRING) { + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_STRING; + node->string = asLowerCaseString(script.getString()); + script.nextToken(); + return node; + } + + if (script.Token == SPECIAL) { + if (script.getSpecial() != '%') { + script.error("illegal character"); + return nullptr; + } + + NpcBehaviourNode* node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_MESSAGE_COUNT; + node->number = script.readNumber(); + script.nextToken(); + return node; + } + + NpcBehaviourNode* node = nullptr; + NpcBehaviourParameterSearch_t searchType = BEHAVIOUR_PARAMETER_NONE; + + std::string identifier = script.getIdentifier(); + if (identifier == "topic") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_TOPIC; + } else if (identifier == "price") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_PRICE; + } else if (identifier == "type") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_ITEM; + } else if (identifier == "string") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_TEXT; + } else if (identifier == "data") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_DATA; + } else if (identifier == "amount") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_AMOUNT; + } else if (identifier == "countmoney") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_COUNTMONEY; + } else if (identifier == "hp") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_HEALTH; + } else if (identifier == "burning") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_BURNING; + } else if (identifier == "level") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_LEVEL; + } else if (identifier == "poison") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_POISON; + } else if (identifier == "balance") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_BALANCE; + } else if (identifier == "spellknown") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_SPELLKNOWN; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "spelllevel") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_SPELLLEVEL; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "questvalue") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_QUESTVALUE; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "count") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_COUNT; + searchType = BEHAVIOUR_PARAMETER_ONE; + } else if (identifier == "random") { + node = new NpcBehaviourNode(); + node->type = BEHAVIOUR_TYPE_RANDOM; + searchType = BEHAVIOUR_PARAMETER_TWO; + } + + if (searchType == BEHAVIOUR_PARAMETER_ONE) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->left = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + } + } else if (searchType == BEHAVIOUR_PARAMETER_TWO) { + script.readSymbol('('); + script.nextToken(); + NpcBehaviourNode* nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->left = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + script.error("',' expected"); + } + script.nextToken(); + nextNode = readValue(script); + nextNode = readFactor(script, nextNode); + node->right = nextNode; + if (script.Token != SPECIAL || script.getSpecial() != ')') { + script.error("')' expected"); + } + } + + if (!node) { + script.error("unknown value"); + } + + script.nextToken(); + return node; +} + +NpcBehaviourNode* BehaviourDatabase::readFactor(ScriptReader& script, NpcBehaviourNode* nextNode) +{ + // * operator + while (true) { + if (script.Token != SPECIAL) { + break; + } + + if (script.getSpecial() != '*') { + break; + } + + NpcBehaviourNode* headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = BEHAVIOUR_OPERATOR_MULTIPLY; + headNode->left = nextNode; + + script.nextToken(); + nextNode = readValue(script); + + headNode->right = nextNode; + nextNode = headNode; + } + + // + - operators + while (true) { + if (script.Token != SPECIAL) { + break; + } + + if (script.getSpecial() != '+' && script.getSpecial() != '-') { + break; + } + + NpcBehaviourNode* headNode = new NpcBehaviourNode(); + headNode->type = BEHAVIOUR_TYPE_OPERATION; + headNode->number = BEHAVIOUR_OPERATOR_SUM; + if (script.getSpecial() == '-') { + headNode->number = BEHAVIOUR_OPERATOR_RES; + } + + headNode->left = nextNode; + script.nextToken(); + nextNode = readValue(script); + + headNode->right = nextNode; + nextNode = headNode; + } + + return nextNode; +} + +void BehaviourDatabase::react(BehaviourSituation_t situation, Player* player, const std::string& message) +{ + for (NpcBehaviour* behaviour : behaviourEntries) { + bool fulfilled = true; + + if (situation == SITUATION_ADDRESS && behaviour->situation != SITUATION_ADDRESS) { + continue; + } + + if (situation == SITUATION_BUSY && behaviour->situation != SITUATION_BUSY) { + continue; + } + + if (situation == SITUATION_VANISH && behaviour->situation != SITUATION_VANISH) { + continue; + } + + if (situation == SITUATION_NONE && behaviour->situation != SITUATION_NONE) { + continue; + } + + for (const NpcBehaviourCondition* condition : behaviour->conditions) { + if (!checkCondition(condition, player, message)) { + fulfilled = false; + break; + } + } + + if (!fulfilled) { + continue; + } + + if (player->getID() == npc->focusCreature) { + topic = 0; + } + + reset(); + + if (situation == SITUATION_ADDRESS || npc->focusCreature == player->getID()) { + attendCustomer(player->getID()); + } + + if (situation == SITUATION_VANISH) { + npc->conversationEndTime = 0; + idle(); + } + + for (const NpcBehaviourAction* action : behaviour->actions) { + checkAction(action, player, message); + } + + break; + } +} + +bool BehaviourDatabase::checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message) +{ + switch (condition->type) { + case BEHAVIOUR_TYPE_NOP: break; + case BEHAVIOUR_TYPE_MESSAGE_COUNT: { + int32_t value = searchDigit(message); + if (value < condition->number) { + return false; + } + break; + } + case BEHAVIOUR_TYPE_STRING: + if (!searchWord(condition->string, message)) { + return false; + } + break; + case BEHAVIOUR_TYPE_SORCERER: + if (player->getVocationId() != 1 && player->getVocationId() != 5) { + return false; + } + break; + case BEHAVIOUR_TYPE_DRUID: + if (player->getVocationId() != 2 && player->getVocationId() != 6) { + return false; + } + break; + case BEHAVIOUR_TYPE_PALADIN: + if (player->getVocationId() != 3 && player->getVocationId() != 7) { + return false; + } + break; + case BEHAVIOUR_TYPE_KNIGHT: + if (player->getVocationId() != 4 && player->getVocationId() != 8) { + return false; + } + break; + case BEHAVIOUR_TYPE_ISPREMIUM: + if (!player->isPremium()) { + return false; + } + break; + case BEHAVIOUR_TYPE_PVPENFORCED: + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + return false; + } + break; + case BEHAVIOUR_TYPE_FEMALE: + if (player->getSex() != PLAYERSEX_FEMALE) { + return false; + } + break; + case BEHAVIOUR_TYPE_MALE: + if (player->getSex() != PLAYERSEX_MALE) { + return false; + } + break; + case BEHAVIOUR_TYPE_PZLOCKED: + if (!player->isPzLocked()) { + return false; + } + break; + case BEHAVIOUR_TYPE_PROMOTED: { + int32_t value = 0; + player->getStorageValue(30018, value); + if (value != 1) { + return false; + } + break; + } + case BEHAVIOUR_TYPE_OPERATION: + return checkOperation(player, condition->expression, message) > 0; + case BEHAVIOUR_TYPE_SPELLKNOWN: { + if (!player->hasLearnedInstantSpell(string)) { + return false; + } + + break; + } + default: + std::cout << "[Warning - BehaviourDatabase::react]: Unhandled node type " << condition->type << std::endl; + return false; + } + + return true; +} + +void BehaviourDatabase::checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message) +{ + switch (action->type) { + case BEHAVIOUR_TYPE_NOP: break; + case BEHAVIOUR_TYPE_STRING: { + delayedEvents.push_back(g_scheduler.addEvent(createSchedulerTask(delay, std::bind(&Npc::doSay, npc, parseResponse(player, action->string))))); + delay += 100 * (message.length() / 5) + 10000; + break; + } + case BEHAVIOUR_TYPE_IDLE: + idle(); + break; + case BEHAVIOUR_TYPE_QUEUE: + queueCustomer(player->getID(), message); + break; + case BEHAVIOUR_TYPE_TOPIC: + topic = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_PRICE: + price = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_DATA: + data = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_ITEM: + type = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_AMOUNT: + amount = evaluate(action->expression, player, message); + break; + case BEHAVIOUR_TYPE_TEXT: + string = action->expression->string; + break; + case BEHAVIOUR_TYPE_HEALTH: { + int32_t newHealth = evaluate(action->expression, player, message); + player->changeHealth(-player->getHealth() + newHealth); + break; + } + case BEHAVIOUR_TYPE_CREATEMONEY: + g_game.addMoney(player, price); + break; + case BEHAVIOUR_TYPE_DELETEMONEY: + g_game.removeMoney(player, price); + break; + case BEHAVIOUR_TYPE_CREATE: { + int32_t itemId = evaluate(action->expression, player, message); + const ItemType& it = Item::items[itemId]; + + if (it.stackable) { + do { + int32_t count = std::min(100, amount); + amount -= count; + + Item* item = Item::CreateItem(itemId, count); + if (!item) { + break; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } while (amount); + } else { + if (it.charges) { + data = it.charges; + } + + for (int32_t i = 0; i < std::max(1, amount); i++) { + Item* item = Item::CreateItem(itemId, data); + if (!item) { + break; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item); + if (ret != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + + break; + } + case BEHAVIOUR_TYPE_DELETE: { + type = evaluate(action->expression, player, message); + const ItemType& itemType = Item::items[type]; + if (itemType.stackable || !itemType.hasSubType()) { + data = -1; + } + + if (!player->removeItemOfType(type, amount, data, true)) { + player->removeItemOfType(type, amount, data, false); + } + break; + } + case BEHAVIOUR_TYPE_EFFECTME: + g_game.addMagicEffect(npc->getPosition(), evaluate(action->expression, player, message)); + break; + case BEHAVIOUR_TYPE_EFFECTOPP: + g_game.addMagicEffect(player->getPosition(), evaluate(action->expression, player, message)); + break; + case BEHAVIOUR_TYPE_BURNING: { + const int32_t cycles = evaluate(action->expression, player, message); + const int32_t count = evaluate(action->expression2, player, message); + + if (cycles == 0) { + player->removeCondition(CONDITION_FIRE, true); + break; + } + + ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + player->addCondition(conditionDamage); + break; + } + case BEHAVIOUR_TYPE_POISON: { + const int32_t cycles = evaluate(action->expression, player, message); + const int32_t count = evaluate(action->expression2, player, message); + + if (cycles == 0) { + player->removeCondition(CONDITION_POISON, true); + break; + } + + ConditionDamage* conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + player->addCondition(conditionDamage); + break; + } + case BEHAVIOUR_TYPE_TOWN: + player->setTown(g_game.map.towns.getTown(evaluate(action->expression, player, message))); + break; + case BEHAVIOUR_TYPE_TEACHSPELL: + player->learnInstantSpell(string); + break; + case BEHAVIOUR_TYPE_QUESTVALUE: { + int32_t questNumber = evaluate(action->expression, player, message); + int32_t questValue = evaluate(action->expression2, player, message); + player->addStorageValue(questNumber, questValue); + break; + } + case BEHAVIOUR_TYPE_TELEPORT: { + Position pos; + pos.x = evaluate(action->expression, player, message); + pos.y = evaluate(action->expression2, player, message); + pos.z = evaluate(action->expression3, player, message); + g_game.internalTeleport(player, pos); + break; + } + case BEHAVIOUR_TYPE_PROFESSION: { + int32_t newVocation = evaluate(action->expression, player, message); + player->setVocation(newVocation); + break; + } + case BEHAVIOUR_TYPE_PROMOTE: { + int32_t newVocation = player->getVocationId() + 4; + player->setVocation(newVocation); + player->addStorageValue(30018, 1); + break; + } + case BEHAVIOUR_TYPE_SUMMON: { + std::string name = action->expression->string; + + Monster* monster = Monster::createMonster(name); + if (!monster) { + break; + } + + if (!g_game.placeCreature(monster, npc->getPosition(), true, true)) { + delete monster; + } + + break; + } + case BEHAVIOUR_TYPE_EXPERIENCE: { + int32_t experience = evaluate(action->expression, player, message); + player->addExperience(experience, true, false); + break; + } + case BEHAVIOUR_TYPE_WITHDRAW: { + int32_t money = evaluate(action->expression, player, message); + player->setBankBalance(player->getBankBalance() - money); + break; + } + case BEHAVIOUR_TYPE_DEPOSIT: { + int32_t money = evaluate(action->expression, player, message); + player->setBankBalance(player->getBankBalance() + money); + break; + } + case BEHAVIOUR_TYPE_BLESS: { + uint8_t number = static_cast(evaluate(action->expression, player, message)) - 1; + + if (!player->hasBlessing(number)) { + player->addBlessing(1 << number); + } + break; + } + case BEHAVIOUR_TYPE_CREATECONTAINER: { + int32_t containerId = evaluate(action->expression, player, message); + int32_t itemId = evaluate(action->expression2, player, message); + int32_t data = evaluate(action->expression3, player, message); + + for (int32_t i = 0; i < std::max(1, amount); i++) { + Item* container = Item::CreateItem(containerId); + if (!container) { + std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create container item" << std::endl; + break; + } + + Container* realContainer = container->getContainer(); + for (int32_t c = 0; c < std::max(1, realContainer->capacity()); c++) { + Item* item = Item::CreateItem(itemId, data); + if (!item) { + std::cout << "[Error - BehaviourDatabase::checkAction]: CreateContainer - failed to create item" << std::endl; + break; + } + + realContainer->internalAddThing(item); + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, container); + if (ret != RETURNVALUE_NOERROR) { + delete container; + break; + } + } + + break; + } + default: + std::cout << "[Warning - BehaviourDatabase::checkAction]: Unhandled node type " << action->type << std::endl; + break; + } +} + +int32_t BehaviourDatabase::evaluate(NpcBehaviourNode* node, Player* player, const std::string& message) +{ + switch (node->type) { + case BEHAVIOUR_TYPE_NUMBER: + return node->number; + case BEHAVIOUR_TYPE_TOPIC: + return topic; + case BEHAVIOUR_TYPE_PRICE: + return price; + case BEHAVIOUR_TYPE_DATA: + return data; + case BEHAVIOUR_TYPE_ITEM: + return type; + case BEHAVIOUR_TYPE_AMOUNT: + return amount; + case BEHAVIOUR_TYPE_HEALTH: + return player->getHealth(); + case BEHAVIOUR_TYPE_COUNT: { + int32_t itemId = evaluate(node->left, player, message); + const ItemType& itemType = Item::items[itemId]; + if (itemType.stackable || !itemType.hasSubType()) { + data = -1; + } + return player->getItemTypeCount(itemId, data); + } + case BEHAVIOUR_TYPE_COUNTMONEY: + return player->getMoney(); + case BEHAVIOUR_TYPE_BURNING: { + Condition* condition = player->getCondition(CONDITION_FIRE); + if (!condition) { + return false; + } + + ConditionDamage* damage = static_cast(condition); + if (damage == nullptr) { + return false; + } + + return damage->getTotalDamage(); + } + case BEHAVIOUR_TYPE_POISON: { + Condition* condition = player->getCondition(CONDITION_POISON); + if (!condition) { + return false; + } + + ConditionDamage* damage = static_cast(condition); + if (damage == nullptr) { + return false; + } + + return damage->getTotalDamage(); + } + case BEHAVIOUR_TYPE_LEVEL: + return player->getLevel(); + case BEHAVIOUR_TYPE_RANDOM: { + int32_t min = evaluate(node->left, player, message); + int32_t max = evaluate(node->right, player, message); + return normal_random(min, max); + } + case BEHAVIOUR_TYPE_QUESTVALUE: { + int32_t questNumber = evaluate(node->left, player, message); + int32_t questValue; + player->getStorageValue(questNumber, questValue); + return questValue; + } + case BEHAVIOUR_TYPE_MESSAGE_COUNT: { + int32_t value = searchDigit(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_SPELLKNOWN: { + if (player->hasLearnedInstantSpell(string)) { + return true; + } + + break; + } + case BEHAVIOUR_TYPE_SPELLLEVEL: { + InstantSpell* spell = g_spells->getInstantSpellByName(string); + if (!spell) { + std::cout << "[Warning - BehaviourDatabase::evaluate]: SpellLevel unknown spell " << node->string << std::endl; + return std::numeric_limits::max(); + } + + return spell->getLevel(); + } + default: + std::cout << "[Warning - BehaviourDatabase::evaluate]: Unhandled node type " << node->type << std::endl; + break; + } + + return false; +} + +int32_t BehaviourDatabase::checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message) +{ + int32_t leftResult = evaluate(node->left, player, message); + int32_t rightResult = evaluate(node->right, player, message); + switch (node->number) { + case BEHAVIOUR_OPERATOR_LESSER_THAN: + return leftResult < rightResult; + case BEHAVIOUR_OPERATOR_EQUALS: + return leftResult == rightResult; + case BEHAVIOUR_OPERATOR_GREATER_THAN: + return leftResult > rightResult; + case BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS: + return leftResult >= rightResult; + case BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS: + return leftResult <= rightResult; + case BEHAVIOUR_OPERATOR_NOT_EQUALS: + return leftResult != rightResult; + case BEHAVIOUR_OPERATOR_MULTIPLY: + return leftResult * rightResult; + case BEHAVIOUR_OPERATOR_SUM: + return leftResult + rightResult; + case BEHAVIOUR_OPERATOR_RES: + return leftResult - rightResult; + default: + break; + } + return false; +} + +int32_t BehaviourDatabase::searchDigit(const std::string& message) +{ + int32_t start = -1; + int32_t end = -1; + int32_t value = 0; + int32_t i = -1; + + for (char c : message) { + i++; + if (start == -1 && IsDigit(c)) { + start = i; + } + else if (start != -1 && !IsDigit(c)) { + end = i; + break; + } + } + + try { + value = std::stoi(message.substr(start, end).c_str()); + } + catch (std::invalid_argument) { + return 0; + } + catch (std::out_of_range) { + return 0; + } + + if (value > 500) { + value = 500; + } + + return value; +} + +bool BehaviourDatabase::searchWord(const std::string& pattern, const std::string& message) +{ + if (pattern.empty() || message.empty()) { + return false; + } + + size_t len = pattern.length(); + bool wholeWord = false; + + if (pattern[len - 1] == '$') { + len--; + wholeWord = true; + } + + std::string newPattern = pattern.substr(0, len); + std::string actualMessage = asLowerCaseString(message); + + if (actualMessage.find(newPattern) == std::string::npos) { + return false; + } + + if (wholeWord) { + size_t wordPos = actualMessage.find(newPattern); + size_t wordEnd = wordPos + newPattern.length() - 1; + + if (wordEnd + 1 > actualMessage.length()) { + return false; + } + + if (wordEnd + 1 == actualMessage.length()) { + return true; + } + + if (!isspace(actualMessage[wordEnd + 1])) { + return false; + } + } + + return true; +} + +std::string BehaviourDatabase::parseResponse(Player* player, const std::string& message) +{ + std::string response = message; + replaceString(response, "%A", std::to_string(amount)); + replaceString(response, "%D", std::to_string(data)); + replaceString(response, "%N", player->getName()); + replaceString(response, "%P", std::to_string(price)); + + int32_t worldTime = g_game.getLightHour(); + int32_t hours = std::floor(worldTime / 60); + int32_t minutes = worldTime % 60; + + std::stringstream ss; + ss << hours << ":"; + if (minutes < 10) { + ss << '0' << minutes; + } else { + ss << minutes; + } + + replaceString(response, "%T", ss.str()); + return response; +} + +void BehaviourDatabase::attendCustomer(uint32_t playerId) +{ + std::lock_guard lock(mutex); + + reset(); + npc->conversationStartTime = OTSYS_TIME(); + npc->conversationEndTime = OTSYS_TIME() + 60000; + npc->focusCreature = playerId; +} + +void BehaviourDatabase::queueCustomer(uint32_t playerId, const std::string& message) +{ + std::lock_guard lock(mutex); + + for (NpcQueueEntry entry : queueList) { + if (entry.playerId == playerId) { + return; + } + } + + NpcQueueEntry customer; + customer.playerId = playerId; + customer.text = message; + queueList.push_back(customer); +} + +void BehaviourDatabase::idle() +{ + std::lock_guard lock(mutex); + + if (queueList.empty()) { + if (OTSYS_TIME() - npc->conversationStartTime <= 3000) { + npc->staticMovementTime = OTSYS_TIME() + 5000; + } + + npc->focusCreature = 0; + } else { + while (!queueList.empty()) { + NpcQueueEntry nextCustomer = queueList.front(); + queueList.pop_front(); + Player* player = g_game.getPlayerByID(nextCustomer.playerId); + if (!player) { + continue; + } else { + if (!Position::areInRange<3, 3>(player->getPosition(), npc->getPosition())) { + continue; + } + + react(SITUATION_ADDRESS, player, nextCustomer.text); + return; + } + } + + npc->focusCreature = 0; + } +} + +void BehaviourDatabase::reset() +{ + delay = 1000; + for (uint32_t eventId : delayedEvents) { + g_scheduler.stopEvent(eventId); + } + delayedEvents.clear(); +} + +bool NpcBehaviourCondition::setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string & _string) +{ + type = _type; + number = _number; + string = _string; + return false; +} diff --git a/src/behaviourdatabase.h b/src/behaviourdatabase.h new file mode 100644 index 0000000..73e1856 --- /dev/null +++ b/src/behaviourdatabase.h @@ -0,0 +1,293 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2017 Alejandro Mujica +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF +#define FS_BEHAVIOURDATABASE_H_4E785D4C545C455E1A0C2A9C84D5EFFF + +#include "script.h" +#include "enums.h" + +enum BehaviourSituation_t +{ + SITUATION_ADDRESS = 1, + SITUATION_BUSY, + SITUATION_VANISH, + SITUATION_NONE, +}; + +enum NpcBehaviourType_t +{ + BEHAVIOUR_TYPE_NOP = 0, // returns true on conditions + BEHAVIOUR_TYPE_STRING, // match string, or NPC say + BEHAVIOUR_TYPE_NUMBER, // return a number + BEHAVIOUR_TYPE_OPERATION, // <, =, >, >=, <=, <> + BEHAVIOUR_TYPE_MESSAGE_COUNT, // get quantity in player message + BEHAVIOUR_TYPE_IDLE, // idle npc + BEHAVIOUR_TYPE_QUEUE, // queue talking creature + BEHAVIOUR_TYPE_TOPIC, // get/set topic + BEHAVIOUR_TYPE_PRICE, // get/set price + BEHAVIOUR_TYPE_DATA, // get/set data + BEHAVIOUR_TYPE_ITEM, // get/set item + BEHAVIOUR_TYPE_AMOUNT, // get/set amount + BEHAVIOUR_TYPE_TEXT, // get/set string + BEHAVIOUR_TYPE_HEALTH, // get/set health + BEHAVIOUR_TYPE_COUNT, // count amount of items + BEHAVIOUR_TYPE_CREATEMONEY, // create money + BEHAVIOUR_TYPE_COUNTMONEY, // get player total money + BEHAVIOUR_TYPE_DELETEMONEY, // remove money from player + BEHAVIOUR_TYPE_CREATE, // create item + BEHAVIOUR_TYPE_DELETE, // deletes an item + BEHAVIOUR_TYPE_EFFECTME, // effect on NPC + BEHAVIOUR_TYPE_EFFECTOPP, // effect on player + BEHAVIOUR_TYPE_BURNING, // get/set burning + BEHAVIOUR_TYPE_POISON, // get/set poison + BEHAVIOUR_TYPE_SPELLKNOWN, // check if spell is known + BEHAVIOUR_TYPE_SPELLLEVEL, // get spell level + BEHAVIOUR_TYPE_TEACHSPELL, // player learn spell + BEHAVIOUR_TYPE_LEVEL, // get player level + BEHAVIOUR_TYPE_RANDOM, // random value + BEHAVIOUR_TYPE_QUESTVALUE, // get/set quest value + BEHAVIOUR_TYPE_TELEPORT, // teleport player to position + BEHAVIOUR_TYPE_SORCERER, // get/set vocation + BEHAVIOUR_TYPE_DRUID, // get/set vocation + BEHAVIOUR_TYPE_KNIGHT, // get/set vocation + BEHAVIOUR_TYPE_PALADIN, // get/set vocation + BEHAVIOUR_TYPE_ISPREMIUM, // is account premium + BEHAVIOUR_TYPE_PVPENFORCED, // get world type pvpenforced + BEHAVIOUR_TYPE_MALE, // is player male + BEHAVIOUR_TYPE_FEMALE, // is player female + BEHAVIOUR_TYPE_PZLOCKED, // is player pz locked + BEHAVIOUR_TYPE_PROMOTED, // check if player promoted + BEHAVIOUR_TYPE_PROFESSION, // get/set profession + BEHAVIOUR_TYPE_PROMOTE, // promote the player + BEHAVIOUR_TYPE_SUMMON, // summons a monster + BEHAVIOUR_TYPE_EXPERIENCE, // grant experience to a player + BEHAVIOUR_TYPE_BALANCE, // return player balance + BEHAVIOUR_TYPE_WITHDRAW, // withdraw from player bank balance + BEHAVIOUR_TYPE_DEPOSIT, // deposit x amount of gold + BEHAVIOUR_TYPE_TRANSFER, // transfer x amount of gold + BEHAVIOUR_TYPE_BLESS, // add blessing to player + BEHAVIOUR_TYPE_CREATECONTAINER, // create a container of an item in particular + BEHAVIOUR_TYPE_TOWN, // change player town +}; + +enum NpcBehaviourOperator_t +{ + BEHAVIOUR_OPERATOR_LESSER_THAN = '<', + BEHAVIOUR_OPERATOR_EQUALS = '=', + BEHAVIOUR_OPERATOR_GREATER_THAN = '>', + BEHAVIOUR_OPERATOR_GREATER_OR_EQUALS = 'G', + BEHAVIOUR_OPERATOR_LESSER_OR_EQUALS = 'L', + BEHAVIOUR_OPERATOR_NOT_EQUALS = 'N', + BEHAVIOUR_OPERATOR_MULTIPLY = '*', + BEHAVIOUR_OPERATOR_SUM = '+', + BEHAVIOUR_OPERATOR_RES = '-', +}; + +enum NpcBehaviourParameterSearch_t +{ + BEHAVIOUR_PARAMETER_NONE, + BEHAVIOUR_PARAMETER_ASSIGN, + BEHAVIOUR_PARAMETER_ONE, + BEHAVIOUR_PARAMETER_TWO, + BEHAVIOUR_PARAMETER_THREE, +}; + +class Npc; +class Player; + +struct NpcBehaviourNode +{ + NpcBehaviourType_t type; + int32_t number; + std::string string; + NpcBehaviourNode* left; + NpcBehaviourNode* right; + + NpcBehaviourNode() : type(), number(0), left(nullptr), right(nullptr) { } + ~NpcBehaviourNode() { + delete left; + delete right; + } + + NpcBehaviourNode* clone() { + NpcBehaviourNode* copy = new NpcBehaviourNode(); + copy->type = type; + copy->number = number; + copy->string = string; + if (left) { + copy->left = left->clone(); + } + if (right) { + copy->right = right->clone(); + } + return copy; + } +}; + +struct NpcBehaviourCondition +{ + NpcBehaviourType_t type; + BehaviourSituation_t situation; + std::string string; + int32_t number; + NpcBehaviourNode* expression; + + NpcBehaviourCondition() : type(), situation(SITUATION_NONE), string(), number(0), expression(nullptr) {} + ~NpcBehaviourCondition() { + delete expression; + } + + //non-copyable + NpcBehaviourCondition(const NpcBehaviourCondition&) = delete; + NpcBehaviourCondition& operator=(const NpcBehaviourCondition&) = delete; + + bool setCondition(NpcBehaviourType_t _type, int32_t _number, const std::string& _string); +}; + +struct NpcBehaviourAction +{ + NpcBehaviourType_t type; + std::string string; + int32_t number; + NpcBehaviourNode* expression; + NpcBehaviourNode* expression2; + NpcBehaviourNode* expression3; + + NpcBehaviourAction() : + type(), + string(), + number(0), + expression(nullptr), + expression2(nullptr), + expression3(nullptr) {} + ~NpcBehaviourAction() { + delete expression; + delete expression2; + delete expression3; + } + + NpcBehaviourAction* clone() { + NpcBehaviourAction* copy = new NpcBehaviourAction(); + copy->type = type; + copy->string = string; + copy->number = number; + if (expression) { + copy->expression = expression->clone(); + } + if (expression2) { + copy->expression2 = expression2->clone(); + } + if (expression3) { + copy->expression3 = expression3->clone(); + } + return copy; + } +}; + +struct NpcBehaviour +{ + BehaviourSituation_t situation = SITUATION_NONE; + uint32_t priority = 0; + std::vector conditions; + std::vector actions; + + NpcBehaviour() = default; + ~NpcBehaviour() { + for (auto condition : conditions) { + delete condition; + } + + for (auto action : actions) { + delete action; + } + } + + //non-copyable + NpcBehaviour(const NpcBehaviour&) = delete; + NpcBehaviour& operator=(const NpcBehaviour&) = delete; +}; + +struct NpcQueueEntry +{ + uint32_t playerId; + std::string text; +}; + +class BehaviourDatabase +{ + public: + BehaviourDatabase(Npc* _npc); + ~BehaviourDatabase(); + + // non-copyable + BehaviourDatabase(const BehaviourDatabase&) = delete; + BehaviourDatabase& operator=(const BehaviourDatabase&) = delete; + + bool loadDatabase(ScriptReader& script); + bool loadBehaviour(ScriptReader& script); + bool loadConditions(ScriptReader& script, NpcBehaviour* behaviour); + bool loadActions(ScriptReader& script, NpcBehaviour* behaviour); + NpcBehaviourNode* readValue(ScriptReader& script); + NpcBehaviourNode* readFactor(ScriptReader& script, NpcBehaviourNode* nextNode); + + void react(BehaviourSituation_t situation, Player* player, const std::string& message); + + static bool compareBehaviour(const NpcBehaviour* left, const NpcBehaviour* right) { + return left->priority >= right->priority; + } + + private: + + bool checkCondition(const NpcBehaviourCondition* condition, Player* player, const std::string& message); + void checkAction(const NpcBehaviourAction* action, Player* player, const std::string& message); + + int32_t evaluate(NpcBehaviourNode* node, Player* player, const std::string& message = ""); + + int32_t checkOperation(Player* player, NpcBehaviourNode* node, const std::string& message); + int32_t searchDigit(const std::string& message); + bool searchWord(const std::string& pattern, const std::string& message); + + std::string parseResponse(Player* player, const std::string& message); + void attendCustomer(uint32_t playerId); + void queueCustomer(uint32_t playerId, const std::string& message); + void idle(); + void reset(); + + int32_t topic; + int32_t data; + int32_t type; + int32_t price; + int32_t amount; + int32_t delay; + + std::string string; + + Npc* npc = nullptr; + NpcBehaviour* previousBehaviour = nullptr; + NpcBehaviour* priorityBehaviour = nullptr; + + std::list queueList; + std::vector delayedEvents; + std::list behaviourEntries; + std::recursive_mutex mutex; + +}; + +#endif diff --git a/src/chat.cpp b/src/chat.cpp new file mode 100644 index 0000000..bee075e --- /dev/null +++ b/src/chat.cpp @@ -0,0 +1,591 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "chat.h" +#include "game.h" +#include "pugicast.h" +#include "scheduler.h" + +extern Chat* g_chat; +extern Game g_game; + +bool PrivateChatChannel::isInvited(uint32_t guid) const +{ + if (guid == getOwner()) { + return true; + } + return invites.find(guid) != invites.end(); +} + +bool PrivateChatChannel::removeInvite(uint32_t guid) +{ + return invites.erase(guid) != 0; +} + +void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer) +{ + auto result = invites.emplace(invitePlayer.getGUID(), &invitePlayer); + if (!result.second) { + return; + } + + std::ostringstream ss; + ss << player.getName() << " invites you to " << (player.getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " private chat channel."; + invitePlayer.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + ss.str(std::string()); + ss << invitePlayer.getName() << " has been invited."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + +void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer) +{ + if (!removeInvite(excludePlayer.getGUID())) { + return; + } + + removeUser(excludePlayer); + + std::ostringstream ss; + ss << excludePlayer.getName() << " has been excluded."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + excludePlayer.sendClosePrivate(id); +} + +void PrivateChatChannel::closeChannel() const +{ + for (const auto& it : users) { + it.second->sendClosePrivate(id); + } +} + +bool ChatChannel::addUser(Player& player) +{ + if (users.find(player.getID()) != users.end()) { + return false; + } + + if (!executeOnJoinEvent(player)) { + return false; + } + + users[player.getID()] = &player; + return true; +} + +bool ChatChannel::removeUser(const Player& player) +{ + auto iter = users.find(player.getID()); + if (iter == users.end()) { + return false; + } + + users.erase(iter); + + executeOnLeaveEvent(player); + return true; +} + +bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text) +{ + if (users.find(fromPlayer.getID()) == users.end()) { + return false; + } + + for (const auto& it : users) { + it.second->sendToChannel(&fromPlayer, type, text, id); + } + return true; +} + +bool ChatChannel::executeCanJoinEvent(const Player& player) +{ + if (canJoinEvent == -1) { + return true; + } + + //canJoin(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CanJoinChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(canJoinEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(canJoinEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnJoinEvent(const Player& player) +{ + if (onJoinEvent == -1) { + return true; + } + + //onJoin(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnJoinChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onJoinEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onJoinEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnLeaveEvent(const Player& player) +{ + if (onLeaveEvent == -1) { + return true; + } + + //onLeave(player) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnLeaveChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onLeaveEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onLeaveEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + return scriptInterface->callFunction(1); +} + +bool ChatChannel::executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message) +{ + if (onSpeakEvent == -1) { + return true; + } + + //onSpeak(player, type, message) + LuaScriptInterface* scriptInterface = g_chat->getScriptInterface(); + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - OnSpeakChannelEvent::execute] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(onSpeakEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(onSpeakEvent); + LuaScriptInterface::pushUserdata(L, &player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, type); + LuaScriptInterface::pushString(L, message); + + bool result = false; + int size0 = lua_gettop(L); + int ret = scriptInterface->protectedCall(L, 3, 1); + if (ret != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else if (lua_gettop(L) > 0) { + if (lua_isboolean(L, -1)) { + result = LuaScriptInterface::getBoolean(L, -1); + } else if (lua_isnumber(L, -1)) { + result = true; + type = LuaScriptInterface::getNumber(L, -1); + } + lua_pop(L, 1); + } + + if ((lua_gettop(L) + 4) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + scriptInterface->resetScriptEnv(); + return result; +} + +Chat::Chat(): + scriptInterface("Chat Interface"), + dummyPrivate(CHANNEL_PRIVATE, "Private Chat Channel") +{ + scriptInterface.initState(); +} + +bool Chat::load() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/chatchannels/chatchannels.xml"); + if (!result) { + printXMLError("Error - Chat::load", "data/chatchannels/chatchannels.xml", result); + return false; + } + + std::forward_list removedChannels; + for (auto& channelEntry : normalChannels) { + ChatChannel& channel = channelEntry.second; + channel.onSpeakEvent = -1; + channel.canJoinEvent = -1; + channel.onJoinEvent = -1; + channel.onLeaveEvent = -1; + removedChannels.push_front(channelEntry.first); + } + + for (auto channelNode : doc.child("channels").children()) { + ChatChannel channel(pugi::cast(channelNode.attribute("id").value()), channelNode.attribute("name").as_string()); + channel.publicChannel = channelNode.attribute("public").as_bool(); + + pugi::xml_attribute scriptAttribute = channelNode.attribute("script"); + if (scriptAttribute) { + if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { + channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); + channel.canJoinEvent = scriptInterface.getEvent("canJoin"); + channel.onJoinEvent = scriptInterface.getEvent("onJoin"); + channel.onLeaveEvent = scriptInterface.getEvent("onLeave"); + } else { + std::cout << "[Warning - Chat::load] Can not load script: " << scriptAttribute.as_string() << std::endl; + } + } + + removedChannels.remove(channel.id); + normalChannels[channel.id] = channel; + } + + for (uint16_t channelId : removedChannels) { + normalChannels.erase(channelId); + } + return true; +} + +ChatChannel* Chat::createChannel(const Player& player, uint16_t channelId) +{ + if (getChannel(player, channelId)) { + return nullptr; + } + + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (guild) { + auto ret = guildChannels.emplace(std::make_pair(guild->getId(), ChatChannel(channelId, guild->getName()))); + return &ret.first->second; + } + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (party) { + auto ret = partyChannels.emplace(std::make_pair(party, ChatChannel(channelId, "Party"))); + return &ret.first->second; + } + break; + } + + case CHANNEL_PRIVATE: { + //only 1 private channel for each premium player + if (!player.isPremium() || getPrivateChannel(player)) { + return nullptr; + } + + //find a free private channel slot + for (uint16_t i = 100; i < 10000; ++i) { + auto ret = privateChannels.emplace(std::make_pair(i, PrivateChatChannel(i, player.getName() + "'s Channel"))); + if (ret.second) { //second is a bool that indicates that a new channel has been placed in the map + auto& newChannel = (*ret.first).second; + newChannel.setOwner(player.getGUID()); + return &newChannel; + } + } + break; + } + + default: + break; + } + return nullptr; +} + +bool Chat::deleteChannel(const Player& player, uint16_t channelId) +{ + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (!guild) { + return false; + } + + auto it = guildChannels.find(guild->getId()); + if (it == guildChannels.end()) { + return false; + } + + guildChannels.erase(it); + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (!party) { + return false; + } + + auto it = partyChannels.find(party); + if (it == partyChannels.end()) { + return false; + } + + partyChannels.erase(it); + break; + } + + default: { + auto it = privateChannels.find(channelId); + if (it == privateChannels.end()) { + return false; + } + + it->second.closeChannel(); + + privateChannels.erase(it); + break; + } + } + return true; +} + +ChatChannel* Chat::addUserToChannel(Player& player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (channel && channel->addUser(player)) { + return channel; + } + return nullptr; +} + +bool Chat::removeUserFromChannel(const Player& player, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (!channel || !channel->removeUser(player)) { + return false; + } + + if (channel->getOwner() == player.getGUID()) { + deleteChannel(player, channelId); + } + return true; +} + +void Chat::removeUserFromAllChannels(const Player& player) +{ + for (auto& it : normalChannels) { + it.second.removeUser(player); + } + + for (auto& it : partyChannels) { + it.second.removeUser(player); + } + + for (auto& it : guildChannels) { + it.second.removeUser(player); + } + + auto it = privateChannels.begin(); + while (it != privateChannels.end()) { + PrivateChatChannel* channel = &it->second; + channel->removeInvite(player.getGUID()); + channel->removeUser(player); + if (channel->getOwner() == player.getGUID()) { + channel->closeChannel(); + it = privateChannels.erase(it); + } else { + ++it; + } + } +} + +bool Chat::talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + ChatChannel* channel = getChannel(player, channelId); + if (!channel) { + return false; + } + + if (channelId == CHANNEL_GUILD) { + const GuildRank* rank = player.getGuildRank(); + if (rank && rank->level > 1) { + type = TALKTYPE_CHANNEL_O; + } else if (type != TALKTYPE_CHANNEL_Y) { + type = TALKTYPE_CHANNEL_Y; + } + } else if (type != TALKTYPE_CHANNEL_Y && (channelId == CHANNEL_PRIVATE || channelId == CHANNEL_PARTY)) { + type = TALKTYPE_CHANNEL_Y; + } + + if (!channel->executeOnSpeakEvent(player, type, text)) { + return false; + } + + return channel->talk(player, type, text); +} + +ChannelList Chat::getChannelList(const Player& player) +{ + ChannelList list; + if (player.getGuild()) { + ChatChannel* channel = getChannel(player, CHANNEL_GUILD); + if (channel) { + list.push_back(channel); + } else { + channel = createChannel(player, CHANNEL_GUILD); + if (channel) { + list.push_back(channel); + } + } + } + + if (player.getParty()) { + ChatChannel* channel = getChannel(player, CHANNEL_PARTY); + if (channel) { + list.push_back(channel); + } else { + channel = createChannel(player, CHANNEL_PARTY); + if (channel) { + list.push_back(channel); + } + } + } + + for (const auto& it : normalChannels) { + ChatChannel* channel = getChannel(player, it.first); + if (channel) { + list.push_back(channel); + } + } + + bool hasPrivate = false; + for (auto& it : privateChannels) { + if (PrivateChatChannel* channel = &it.second) { + uint32_t guid = player.getGUID(); + if (channel->isInvited(guid)) { + list.push_back(channel); + } + + if (channel->getOwner() == guid) { + hasPrivate = true; + } + } + } + + if (!hasPrivate && player.isPremium()) { + list.push_front(&dummyPrivate); + } + return list; +} + +ChatChannel* Chat::getChannel(const Player& player, uint16_t channelId) +{ + switch (channelId) { + case CHANNEL_GUILD: { + Guild* guild = player.getGuild(); + if (guild) { + auto it = guildChannels.find(guild->getId()); + if (it != guildChannels.end()) { + return &it->second; + } + } + break; + } + + case CHANNEL_PARTY: { + Party* party = player.getParty(); + if (party) { + auto it = partyChannels.find(party); + if (it != partyChannels.end()) { + return &it->second; + } + } + break; + } + + default: { + auto it = normalChannels.find(channelId); + if (it != normalChannels.end()) { + ChatChannel& channel = it->second; + if (!channel.executeCanJoinEvent(player)) { + return nullptr; + } + return &channel; + } else { + auto it2 = privateChannels.find(channelId); + if (it2 != privateChannels.end() && it2->second.isInvited(player.getGUID())) { + return &it2->second; + } + } + break; + } + } + return nullptr; +} + +ChatChannel* Chat::getGuildChannelById(uint32_t guildId) +{ + auto it = guildChannels.find(guildId); + if (it == guildChannels.end()) { + return nullptr; + } + return &it->second; +} + +ChatChannel* Chat::getChannelById(uint16_t channelId) +{ + auto it = normalChannels.find(channelId); + if (it == normalChannels.end()) { + return nullptr; + } + return &it->second; +} + +PrivateChatChannel* Chat::getPrivateChannel(const Player& player) +{ + for (auto& it : privateChannels) { + if (it.second.getOwner() == player.getGUID()) { + return &it.second; + } + } + return nullptr; +} diff --git a/src/chat.h b/src/chat.h new file mode 100644 index 0000000..3d7b973 --- /dev/null +++ b/src/chat.h @@ -0,0 +1,161 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 +#define FS_CHAT_H_F1574642D0384ABFAB52B7ED906E5628 + +#include "const.h" +#include "luascript.h" + +class Party; +class Player; + +typedef std::map UsersMap; +typedef std::map InvitedMap; + +class ChatChannel +{ + public: + ChatChannel() = default; + ChatChannel(uint16_t channelId, std::string channelName): + name(std::move(channelName)), + id(channelId) {} + + virtual ~ChatChannel() = default; + + bool addUser(Player& player); + bool removeUser(const Player& player); + + bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); + + const std::string& getName() const { + return name; + } + uint16_t getId() const { + return id; + } + const UsersMap& getUsers() const { + return users; + } + virtual const InvitedMap* getInvitedUsers() const { + return nullptr; + } + + virtual uint32_t getOwner() const { + return 0; + } + + bool isPublicChannel() const { return publicChannel; } + + bool executeOnJoinEvent(const Player& player); + bool executeCanJoinEvent(const Player& player); + bool executeOnLeaveEvent(const Player& player); + bool executeOnSpeakEvent(const Player& player, SpeakClasses& type, const std::string& message); + + protected: + UsersMap users; + + std::string name; + + int32_t canJoinEvent = -1; + int32_t onJoinEvent = -1; + int32_t onLeaveEvent = -1; + int32_t onSpeakEvent = -1; + + uint16_t id; + bool publicChannel = false; + + friend class Chat; +}; + +class PrivateChatChannel final : public ChatChannel +{ + public: + PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} + + uint32_t getOwner() const final { + return owner; + } + void setOwner(uint32_t owner) { + this->owner = owner; + } + + bool isInvited(uint32_t guid) const; + + void invitePlayer(const Player& player, Player& invitePlayer); + void excludePlayer(const Player& player, Player& excludePlayer); + + bool removeInvite(uint32_t guid); + + void closeChannel() const; + + const InvitedMap* getInvitedUsers() const final { + return &invites; + } + + protected: + InvitedMap invites; + uint32_t owner = 0; +}; + +typedef std::list ChannelList; + +class Chat +{ + public: + Chat(); + + // non-copyable + Chat(const Chat&) = delete; + Chat& operator=(const Chat&) = delete; + + bool load(); + + ChatChannel* createChannel(const Player& player, uint16_t channelId); + bool deleteChannel(const Player& player, uint16_t channelId); + + ChatChannel* addUserToChannel(Player& player, uint16_t channelId); + bool removeUserFromChannel(const Player& player, uint16_t channelId); + void removeUserFromAllChannels(const Player& player); + + bool talkToChannel(const Player& player, SpeakClasses type, const std::string& text, uint16_t channelId); + + ChannelList getChannelList(const Player& player); + + ChatChannel* getChannel(const Player& player, uint16_t channelId); + ChatChannel* getChannelById(uint16_t channelId); + ChatChannel* getGuildChannelById(uint32_t guildId); + PrivateChatChannel* getPrivateChannel(const Player& player); + + LuaScriptInterface* getScriptInterface() { + return &scriptInterface; + } + + private: + std::map normalChannels; + std::map privateChannels; + std::map partyChannels; + std::map guildChannels; + + LuaScriptInterface scriptInterface; + + PrivateChatChannel dummyPrivate; +}; + +#endif diff --git a/src/combat.cpp b/src/combat.cpp new file mode 100644 index 0000000..4d850b6 --- /dev/null +++ b/src/combat.cpp @@ -0,0 +1,1911 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "combat.h" + +#include "game.h" +#include "configmanager.h" +#include "monster.h" + +extern Game g_game; +extern ConfigManager g_config; + +CombatDamage Combat::getCombatDamage(Creature* creature) const +{ + CombatDamage damage; + damage.type = params.combatType; + if (formulaType == COMBAT_FORMULA_DAMAGE) { + damage.min = static_cast(mina); + damage.max = static_cast(maxa); + } else if (creature) { + int32_t min, max; + if (creature->getCombatValues(min, max)) { + damage.min = min; + damage.max = max; + } else if (Player* player = creature->getPlayer()) { + if (params.valueCallback) { + params.valueCallback->getMinMaxValues(player, damage, params.useCharges); + } + } + } + return damage; +} + +void Combat::getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list& list) +{ + if (targetPos.z >= MAP_MAX_LAYERS) { + return; + } + + if (area) { + area->getList(centerPos, targetPos, list); + } else { + Tile* tile = g_game.map.getTile(targetPos); + if (!tile) { + tile = new StaticTile(targetPos.x, targetPos.y, targetPos.z); + g_game.map.setTile(targetPos, tile); + } + list.push_front(tile); + } +} + +CombatType_t Combat::ConditionToDamageType(ConditionType_t type) +{ + switch (type) { + case CONDITION_FIRE: + return COMBAT_FIREDAMAGE; + + case CONDITION_ENERGY: + return COMBAT_ENERGYDAMAGE; + + case CONDITION_POISON: + return COMBAT_EARTHDAMAGE; + + default: + break; + } + + return COMBAT_NONE; +} + +ConditionType_t Combat::DamageToConditionType(CombatType_t type) +{ + switch (type) { + case COMBAT_FIREDAMAGE: + return CONDITION_FIRE; + + case COMBAT_ENERGYDAMAGE: + return CONDITION_ENERGY; + + case COMBAT_EARTHDAMAGE: + return CONDITION_POISON; + + default: + return CONDITION_NONE; + } +} + +bool Combat::isPlayerCombat(const Creature* target) +{ + if (target->getPlayer()) { + return true; + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + return true; + } + + return false; +} + +ReturnValue Combat::canTargetCreature(Player* player, Creature* target) +{ + if (player == target) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (!player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + //pz-zone + if (player->getZone() == ZONE_PROTECTION) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; + } + + if (target->getZone() == ZONE_PROTECTION) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + + //nopvp-zone + if (isPlayerCombat(target)) { + if (player->getZone() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (target->getZone() == ZONE_NOPVP) { + return RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE; + } + } + } + + if (player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { + if (target->getPlayer()) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } else { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + } + + if (target->getPlayer()) { + if (isProtected(player, target->getPlayer())) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (player->hasSecureMode() && !Combat::isInPvpZone(player, target) && player->getSkullClient(target->getPlayer()) == SKULL_NONE) { + return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; + } + } + + return Combat::canDoCombat(player, target); +} + +ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) +{ + if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE) && tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && tile->hasProperty(CONST_PROP_UNLAY)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->getTeleportItem()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (caster) { + const Position& casterPosition = caster->getPosition(); + const Position& tilePosition = tile->getPosition(); + if (casterPosition.z < tilePosition.z) { + return RETURNVALUE_FIRSTGODOWNSTAIRS; + } else if (casterPosition.z > tilePosition.z) { + return RETURNVALUE_FIRSTGOUPSTAIRS; + } + + if (const Player* player = caster->getPlayer()) { + if (player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + return RETURNVALUE_NOERROR; + } + } + } + + //pz-zone + if (aggressive && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE; + } + + return RETURNVALUE_NOERROR; +} + +bool Combat::isInPvpZone(const Creature* attacker, const Creature* target) +{ + return attacker->getZone() == ZONE_PVP && target->getZone() == ZONE_PVP; +} + +bool Combat::isProtected(const Player* attacker, const Player* target) +{ + uint32_t protectionLevel = g_config.getNumber(ConfigManager::PROTECTION_LEVEL); + if (target->getLevel() < protectionLevel || attacker->getLevel() < protectionLevel) { + return true; + } + + if (attacker->getVocationId() == VOCATION_NONE || target->getVocationId() == VOCATION_NONE) { + return true; + } + + return false; +} + +ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) +{ + if (attacker) { + if (const Player* targetPlayer = target->getPlayer()) { + if (targetPlayer->hasFlag(PlayerFlag_CannotBeAttacked)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (isProtected(attackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + //nopvp-zone + const Tile* targetPlayerTile = targetPlayer->getTile(); + if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } + + if (attacker->isSummon()) { + if (const Player* masterAttackerPlayer = attacker->getMaster()->getPlayer()) { + if (masterAttackerPlayer->hasFlag(PlayerFlag_CannotAttackPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + + if (targetPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + + if (isProtected(masterAttackerPlayer, targetPlayer)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + } + } + } else if (target->getMonster()) { + if (const Player* attackerPlayer = attacker->getPlayer()) { + if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + + if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) { + return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; + } + } + } + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attacker->getPlayer() || (attacker->isSummon() && attacker->getMaster()->getPlayer())) { + if (target->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; + } + } + + if (target->isSummon() && target->getMaster()->getPlayer()) { + if (!isInPvpZone(attacker, target)) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + } + } + } + } + return RETURNVALUE_NOERROR; +} + +void Combat::setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb) +{ + this->formulaType = formulaType; + this->mina = mina; + this->minb = minb; + this->maxa = maxa; + this->maxb = maxb; +} + +bool Combat::setParam(CombatParam_t param, uint32_t value) +{ + switch (param) { + case COMBAT_PARAM_TYPE: { + params.combatType = static_cast(value); + return true; + } + + case COMBAT_PARAM_EFFECT: { + params.impactEffect = static_cast(value); + return true; + } + + case COMBAT_PARAM_DISTANCEEFFECT: { + params.distanceEffect = static_cast(value); + return true; + } + + case COMBAT_PARAM_BLOCKARMOR: { + params.blockedByArmor = (value != 0); + return true; + } + + case COMBAT_PARAM_BLOCKSHIELD: { + params.blockedByShield = (value != 0); + return true; + } + + case COMBAT_PARAM_TARGETCASTERORTOPMOST: { + params.targetCasterOrTopMost = (value != 0); + return true; + } + + case COMBAT_PARAM_CREATEITEM: { + params.itemId = value; + return true; + } + + case COMBAT_PARAM_AGGRESSIVE: { + params.aggressive = (value != 0); + return true; + } + + case COMBAT_PARAM_DISPEL: { + params.dispelType = static_cast(value); + return true; + } + + case COMBAT_PARAM_USECHARGES: { + params.useCharges = (value != 0); + return true; + } + + case COMBAT_PARAM_DECREASEDAMAGE: { + params.decreaseDamage = static_cast(value); + return true; + } + + case COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE: { + params.maximumDecreasedDamage = static_cast(value); + return true; + } + } + return false; +} + +bool Combat::setCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACK_PARAM_LEVELMAGICVALUE: { + params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_LEVELMAGIC)); + return true; + } + + case CALLBACK_PARAM_SKILLVALUE: { + params.valueCallback.reset(new ValueCallback(COMBAT_FORMULA_SKILL)); + return true; + } + + case CALLBACK_PARAM_TARGETTILE: { + params.tileCallback.reset(new TileCallback()); + return true; + } + + case CALLBACK_PARAM_TARGETCREATURE: { + params.targetCallback.reset(new TargetCallback()); + return true; + } + } + return false; +} + +CallBack* Combat::getCallback(CallBackParam_t key) +{ + switch (key) { + case CALLBACK_PARAM_LEVELMAGICVALUE: + case CALLBACK_PARAM_SKILLVALUE: { + return params.valueCallback.get(); + } + + case CALLBACK_PARAM_TARGETTILE: { + return params.tileCallback.get(); + } + + case CALLBACK_PARAM_TARGETCREATURE: { + return params.targetCallback.get(); + } + } + return nullptr; +} + +bool Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +{ + assert(data); + CombatDamage damage = *data; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); + } + + if (damage.value < 0 && caster) { + Player* targetPlayer = target->getPlayer(); + if (targetPlayer && caster->getPlayer()) { + damage.value /= 2; + } + } + + if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { + return false; + } + + if (g_game.combatChangeHealth(caster, target, damage)) { + CombatConditionFunc(caster, target, params, nullptr); + CombatDispelFunc(caster, target, params, nullptr); + } + + return true; +} + +bool Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +{ + assert(data); + CombatDamage damage = *data; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); + } + + if (damage.value < 0) { + if (caster && caster->getPlayer() && target->getPlayer()) { + damage.value /= 2; + } + } + + if (g_game.combatChangeMana(caster, target, damage.value)) { + CombatConditionFunc(caster, target, params, nullptr); + CombatDispelFunc(caster, target, params, nullptr); + } + + return true; +} + +bool Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage*) +{ + for (const auto& condition : params.conditionList) { + if (caster == target || !target->isImmune(condition->getType())) { + Condition* conditionCopy = condition->clone(); + if (caster) { + conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); + } + + //TODO: infight condition until all aggressive conditions has ended + target->addCombatCondition(conditionCopy); + } + } + + return true; +} + +bool Combat::CombatDispelFunc(Creature*, Creature* target, const CombatParams& params, CombatDamage*) +{ + target->removeCombatCondition(params.dispelType); + return true; +} + +bool Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage*) +{ + CombatConditionFunc(caster, target, params, nullptr); + CombatDispelFunc(caster, target, params, nullptr); + return true; +} + +void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params) +{ + if (params.itemId != 0) { + uint16_t itemId = params.itemId; + switch (itemId) { + case ITEM_FIREFIELD_PERSISTENT_FULL: + itemId = ITEM_FIREFIELD_PVP_FULL; + break; + + case ITEM_FIREFIELD_PERSISTENT_MEDIUM: + itemId = ITEM_FIREFIELD_PVP_MEDIUM; + break; + + case ITEM_FIREFIELD_PERSISTENT_SMALL: + itemId = ITEM_FIREFIELD_PVP_SMALL; + break; + + case ITEM_ENERGYFIELD_PERSISTENT: + itemId = ITEM_ENERGYFIELD_PVP; + break; + + case ITEM_POISONFIELD_PERSISTENT: + itemId = ITEM_POISONFIELD_PVP; + break; + + case ITEM_MAGICWALL_PERSISTENT: + itemId = ITEM_MAGICWALL; + break; + + case ITEM_WILDGROWTH_PERSISTENT: + itemId = ITEM_WILDGROWTH; + break; + + default: + break; + } + + if (caster) { + Player* casterPlayer; + if (caster->isSummon()) { + casterPlayer = caster->getMaster()->getPlayer(); + } else { + casterPlayer = caster->getPlayer(); + } + + if (casterPlayer) { + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || tile->hasFlag(TILESTATE_NOPVPZONE)) { + if (itemId == ITEM_FIREFIELD_PVP_FULL) { + itemId = ITEM_FIREFIELD_NOPVP; + } else if (itemId == ITEM_POISONFIELD_PVP) { + itemId = ITEM_POISONFIELD_NOPVP; + } else if (itemId == ITEM_ENERGYFIELD_PVP) { + itemId = ITEM_ENERGYFIELD_NOPVP; + } + } else if (itemId == ITEM_FIREFIELD_PVP_FULL || itemId == ITEM_POISONFIELD_PVP || itemId == ITEM_ENERGYFIELD_PVP) { + casterPlayer->addInFightTicks(); + } + } + } + + Item* item = Item::CreateItem(itemId); + if (caster) { + item->setOwner(caster->getID()); + } + + ReturnValue ret = g_game.internalAddItem(tile, item); + if (ret == RETURNVALUE_NOERROR) { + g_game.startDecay(item); + } else { + delete item; + } + } + + if (params.tileCallback) { + params.tileCallback->onTileCombat(caster, tile); + } + + if (params.impactEffect != CONST_ME_NONE) { + Game::addMagicEffect(list, tile->getPosition(), params.impactEffect); + } +} + +void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params) +{ + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), pos, params.distanceEffect); + } +} + +void Combat::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +{ + if (effect != CONST_ANI_NONE) { + g_game.addDistanceEffect(fromPos, toPos, effect); + } +} + +void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data) +{ + std::forward_list tileList; + + if (caster) { + getCombatArea(caster->getPosition(), pos, area, tileList); + } else { + getCombatArea(pos, pos, area, tileList); + } + + SpectatorVec list; + uint32_t maxX = 0; + uint32_t maxY = 0; + + //calculate the max viewable range + for (Tile* tile : tileList) { + const Position& tilePos = tile->getPosition(); + + uint32_t diff = Position::getDistanceX(tilePos, pos); + if (diff > maxX) { + maxX = diff; + } + + diff = Position::getDistanceY(tilePos, pos); + if (diff > maxY) { + maxY = diff; + } + } + + const int32_t rangeX = maxX + Map::maxViewportX; + const int32_t rangeY = maxY + Map::maxViewportY; + g_game.map.getSpectators(list, pos, true, true, rangeX, rangeX, rangeY, rangeY); + + uint16_t decreasedDamage = 0; + const uint16_t maximumDecreasedDamage = params.maximumDecreasedDamage; + + bool firstCreature = true; + + if (params.decreaseDamage && data) { + for (Tile* tile : tileList) { + if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + const Creature* topCreature = tile->getTopCreature(); + for (Creature* creature : *creatures) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == tile) { + if (creature != caster) { + continue; + } + } else if (creature != topCreature) { + continue; + } + } + + if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + if (firstCreature) { + firstCreature = false; + continue; + } + + // only apply to players + if (creature->getPlayer()) { + if (maximumDecreasedDamage && decreasedDamage >= maximumDecreasedDamage) { + break; + } + + decreasedDamage += params.decreaseDamage; + } + } + } + } + } + + // actually decrease total damage output + if (data->value == 0) { + int32_t decreasedMinDamage = std::abs(data->min) * decreasedDamage / 100; + int32_t decreasedMaxDamage = std::abs(data->max) * decreasedDamage / 100; + + if (data->min < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->min += decreasedMinDamage; + data->max += decreasedMaxDamage; + + data->min = std::min(0, data->min); + data->max = std::min(0, data->max); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->min -= decreasedMinDamage; + data->max -= decreasedMaxDamage; + + data->min = std::max(0, data->min); + data->max = std::max(0, data->max); + } + } else { + int32_t decreasedValue = (std::abs(data->value) * decreasedDamage) / 100; + + if (data->value < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->value += decreasedValue; + + data->value = std::min(0, data->value); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->value -= decreasedValue; + + data->value = std::max(0, data->value); + } + } + } + + for (Tile* tile : tileList) { + if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + const Creature* topCreature = tile->getTopCreature(); + for (Creature* creature : *creatures) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == tile) { + if (creature != caster) { + continue; + } + } else if (creature != topCreature) { + continue; + } + } + + if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + func(caster, creature, params, data); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, creature); + } + + if (params.targetCasterOrTopMost) { + break; + } + } + } + } + combatTileEffects(list, caster, tile, params); + } + postCombatEffects(caster, pos, params); +} + +void Combat::doCombat(Creature* caster, Creature* target) const +{ + //target combat callback function + if (params.combatType != COMBAT_NONE) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { + doCombatHealth(caster, target, damage, params); + } else { + doCombatMana(caster, target, damage, params); + } + } else { + doCombatDefault(caster, target, params); + } +} + +void Combat::doCombat(Creature* caster, const Position& position) const +{ + //area combat callback function + if (params.combatType != COMBAT_NONE) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { + doCombatHealth(caster, position, area.get(), damage, params); + } else { + doCombatMana(caster, position, area.get(), damage, params); + } + } else { + CombatFunc(caster, position, area.get(), params, CombatNullFunc, nullptr); + } +} + +int32_t Combat::computeDamage(Creature* creature, int32_t strength, int32_t variation) +{ + int32_t damage = strength; + if (variation) { + damage = normal_random(-variation, variation) + strength; + } + + if (creature) { + if (Player* player = creature->getPlayer()) { + int32_t formula = 3 * player->getMagicLevel() + 2 * player->getLevel(); + damage = formula * damage / 100; + } + } + + return damage; +} + +int32_t Combat::getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode) +{ + int32_t damage = attackValue; + + switch (fightMode) { + case FIGHTMODE_ATTACK: + damage += 2 * damage / 10; + break; + case FIGHTMODE_DEFENSE: + damage -= 4 * damage / 10; + break; + default: break; + } + + int32_t formula = (5 * (attackSkill) + 50) * damage; + int32_t randresult = rand() % 100; + int32_t totalDamage = -(ceil(formula * ((rand() % 100 + randresult) / 2) / 10000.)); + return totalDamage; +} + +bool Combat::attack(Creature* attacker, Creature* target) +{ + if (Player* player = attacker->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + if (weapon->getWeaponType() == WEAPON_DISTANCE || weapon->getWeaponType() == WEAPON_WAND) { + return rangeAttack(attacker, target, player->getFightMode()); + } + } + + return closeAttack(attacker, target, player->getFightMode()); + } + + return false; +} + +bool Combat::closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > 1) { + return false; + } + + Item* weapon = nullptr; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (weapon && !Combat::canUseWeapon(player, weapon)) { + return false; + } + } + + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + int32_t defense = target->getDefense(); + + if (OTSYS_TIME() < target->earliestDefendTime) { + defense = 0; + } + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = true; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + int32_t totalDamage = Combat::getTotalDamage(skillValue, attackValue, fightMode); + combatDamage.value = totalDamage; + + bool hit = Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (Monster* monster = attacker->getMonster()) { + int32_t poison = monster->mType->info.poison; + if (poison) { + int32_t randTest = rand(); + + if (hit || -totalDamage > defense && (randTest == 5 * (randTest / 5))) { + poison = normal_random(poison / 2, poison); + if (poison) { + ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_POISON, 0, 0)); + condition->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + condition->setParam(CONDITION_PARAM_CYCLE, poison); + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + target->addCombatCondition(condition); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + // skills advancing + if (!player->hasFlag(PlayerFlag_NotGainSkill)) { + if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { + player->addSkillAdvance(static_cast(skill), 1); + } + } + + // weapon + if (weapon) { + if (weapon->getCharges() > 0) { + int32_t charges = weapon->getCharges() - 1; + if (charges <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), charges); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +bool Combat::rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + uint8_t range = 0; + uint8_t hitChance = 0; + uint8_t distanceEffect = 0; + uint8_t specialEffect = 0; + int32_t attackStrength = 0; + int32_t attackVariation = 0; + + Item* weapon = nullptr; + Item* ammunition = nullptr; + + bool moveWeapon = true; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (!weapon) { + return false; + } + + if (!Combat::canUseWeapon(player, weapon)) { + return false; + } + + range = weapon->getShootRange(); + distanceEffect = weapon->getMissileType(); + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + ammunition = player->getAmmunition(); + if (weapon->getAmmoType() != AMMO_NONE) { + if (!ammunition || weapon->getAmmoType() != ammunition->getAmmoType()) { + // redirect to fist fighting + return closeAttack(attacker, target, fightMode); + } + + distanceEffect = ammunition->getMissileType(); + } + } + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > range) { + return false; + } + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = false; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = Combat::getTotalDamage(skillValue, attackValue, fightMode); + + if (weapon) { + hitChance = 75; // throwables and such + specialEffect = weapon->getWeaponSpecialEffect(); + attackStrength = weapon->getAttackStrength(); + attackVariation = weapon->getAttackVariation(); + if (weapon->getFragility()) { + if (normal_random(0, 99) <= weapon->getFragility()) { + uint16_t count = weapon->getItemCount(); + if (count > 1) { + g_game.transformItem(weapon, weapon->getID(), count - 1); + } else { + g_game.internalRemoveItem(weapon); + } + + moveWeapon = false; + } + } + } + + if (ammunition && weapon->getAmmoType() != AMMO_NONE && weapon->getAmmoType() == ammunition->getAmmoType()) { + hitChance = 90; // bows and crossbows + specialEffect = ammunition->getWeaponSpecialEffect(); + attackStrength = ammunition->getAttackStrength(); + attackVariation = ammunition->getAttackVariation(); + if (normal_random(0, 100) <= ammunition->getFragility()) { + uint16_t count = ammunition->getItemCount(); + if (count > 1) { + g_game.transformItem(ammunition, ammunition->getID(), count - 1); + } else { + g_game.internalRemoveItem(ammunition); + } + } + + moveWeapon = false; + } + + int32_t distance = std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)); + if (distance <= 1) { + distance = 5; + } + + distance *= 15; + + bool hit = false; + + if (rand() % distance <= skillValue) { + hit = rand() % 100 <= hitChance; + } + + if (Player* player = attacker->getPlayer()) { + if (player->getAddAttackSkill()) { + switch (player->getLastAttackBlockType()) { + case BLOCK_NONE: { + player->addSkillAdvance(SKILL_DISTANCE, 2); + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + player->addSkillAdvance(SKILL_DISTANCE, 1); + break; + } + + default: break; + } + } + } + + if (specialEffect == 1) { + if (hit) { + const int32_t rounds = ammunition ? ammunition->getAttackStrength() : weapon->getAttackStrength(); + + ConditionDamage* poisonDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + poisonDamage->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + poisonDamage->setParam(CONDITION_PARAM_CYCLE, rounds); + poisonDamage->setParam(CONDITION_PARAM_COUNT, 3); + poisonDamage->setParam(CONDITION_PARAM_MAX_COUNT, 3); + + target->addCombatCondition(poisonDamage); + } + } else if (specialEffect == 2) { + DamageImpact impact; + impact.actor = attacker; + impact.damage.type = COMBAT_PHYSICALDAMAGE; + impact.damage.value = -Combat::computeDamage(attacker, attackStrength, attackVariation); + impact.params.blockedByArmor = true; + impact.params.blockedByShield = false; + circleShapeSpell(attacker, target->getPosition(), 0xFF, 0, 3, &impact, 7); + } + + if (!hit) { + Tile* destTile = target->getTile(); + + if (!Position::areInRange<1, 1, 0>(attacker->getPosition(), target->getPosition())) { + static std::vector> destList{ + { -1, -1 },{ 0, -1 },{ 1, -1 }, + { -1, 0 },{ 0, 0 },{ 1, 0 }, + { -1, 1 },{ 0, 1 },{ 1, 1 } + }; + std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); + + Position destPos = target->getPosition(); + + for (const auto& dir : destList) { + // Blocking tiles or tiles without ground ain't valid targets for spears + Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); + if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { + destTile = tmpTile; + break; + } + } + } + + g_game.addMagicEffect(destTile->getPosition(), CONST_ME_POFF); + g_game.addDistanceEffect(attackerPos, destTile->getPosition(), distanceEffect); + + if (moveWeapon) { + g_game.internalMoveItem(weapon->getParent(), destTile, INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + + return true; + } + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (moveWeapon) { + g_game.internalMoveItem(weapon->getParent(), target->getTile(), INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + } else if (weapon->getWeaponType() == WEAPON_WAND) { + int32_t variation = normal_random(-weapon->getAttackVariation(), weapon->getAttackVariation()); + + CombatParams combatParams; + combatParams.combatType = weapon->getDamageType(); + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = -(variation + weapon->getAttackStrength()); + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +void Combat::circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect) +{ + const Position& fromPos = attacker->getPosition(); + if (fromPos.z != toPos.z) { + return; + } + + int32_t distance = std::max(Position::getDistanceX(fromPos, toPos), Position::getDistanceY(fromPos, toPos)); + if (distance > range) { + return; + } + + if (animation && fromPos != toPos) { + g_game.addDistanceEffect(fromPos, toPos, animation); + } + + std::forward_list tiles; + + AreaCombat areaCombat; + areaCombat.setupArea(radius); + + areaCombat.getList(toPos, toPos, tiles); + + for (Tile* tile : tiles) { + if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + for (Creature* creature : *creatures) { + impact->handleCreature(creature); + } + } + + if (effect) { + g_game.addMagicEffect(tile->getPosition(), effect); + } + } +} + +void Combat::getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill) +{ + skill = SKILL_FIST; + + if (Player* player = creature->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + switch (weapon->getWeaponType()) { + case WEAPON_AXE: { + skill = SKILL_AXE; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_SWORD: { + skill = SKILL_SWORD; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_CLUB: { + skill = SKILL_CLUB; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_DISTANCE: { + skill = SKILL_DISTANCE; + attackValue = weapon->getAttack(); + + if (weapon->getAmmoType() != AMMO_NONE) { + Item* ammunition = player->getAmmunition(); + if (ammunition && ammunition->getAmmoType() == weapon->getAmmoType()) { + attackValue += ammunition->getAttack(); + } + } + break; + } + default: + attackValue = 7; + break; + } + + skillValue = player->getSkillLevel(skill); + } else { + attackValue = 7; + skillValue = player->getSkillLevel(skill); + } + } else if (Monster* monster = creature->getMonster()) { + attackValue = monster->mType->info.attack; + skillValue = monster->mType->info.skill; + } +} + +bool Combat::canUseWeapon(Player* player, Item* weapon) +{ + if (player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return true; + } + + if (player->getLevel() < weapon->getMinimumLevel()) { + return false; + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < weapon->getManaConsumption()) { + return false; + } + + const ItemType& itemType = Item::items[weapon->getID()]; + if (hasBitSet(WIELDINFO_VOCREQ, itemType.wieldInfo)) { + if (!hasBitSet(player->getVocationFlagId(), itemType.vocations)) { + return false; + } + } + + return true; +} + +void Combat::postWeaponEffects(Player* player, Item* weapon) +{ + if (!weapon || player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return; + } + + int32_t manaConsumption = weapon->getManaConsumption(); + if (manaConsumption) { + player->addManaSpent(manaConsumption); + player->changeMana(-manaConsumption); + } +} + +bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + canCombat = CombatHealthFunc(caster, target, params, &damage); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } + + return canCombat; +} + +void Combat::doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatHealthFunc, &damage); +} + +void Combat::doCombatMana(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + CombatManaFunc(caster, target, params, &damage); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatMana(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatManaFunc, &damage); +} + +void Combat::doCombatCondition(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatConditionFunc, nullptr); +} + +void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + CombatConditionFunc(caster, target, params, nullptr); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatDispel(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params) +{ + CombatFunc(caster, position, area, params, CombatDispelFunc, nullptr); +} + +void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatParams& params) +{ + bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); + if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + + if (canCombat) { + CombatDispelFunc(caster, target, params, nullptr); + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params) +{ + if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR)) { + SpectatorVec list; + g_game.map.getSpectators(list, target->getPosition(), true, true); + + CombatNullFunc(caster, target, params, nullptr); + combatTileEffects(list, caster, target->getTile(), params); + + if (params.targetCallback) { + params.targetCallback->onTargetCombat(caster, target); + } + + /* + if (params.impactEffect != CONST_ME_NONE) { + g_game.addMagicEffect(target->getPosition(), params.impactEffect); + } + */ + + if (caster && params.distanceEffect != CONST_ANI_NONE) { + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + } + } +} + +//**********************************************************// + +void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const +{ + //onGetPlayerMinMaxValues(...) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - ValueCallback::getMinMaxValues] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + int parameters = 1; + switch (type) { + case COMBAT_FORMULA_LEVELMAGIC: { + //onGetPlayerMinMaxValues(player, level, maglevel) + lua_pushnumber(L, player->getLevel()); + lua_pushnumber(L, player->getMagicLevel()); + parameters += 2; + break; + } + + case COMBAT_FORMULA_SKILL: { + //onGetPlayerMinMaxValues(player, attackSkill, attackValue, fightMode) + uint32_t attackValue = 7; + uint32_t attackSkill = 0; + uint8_t skill = 0; + + Combat::getAttackValue(player, attackValue, attackSkill, skill); + + Item* weapon = player->getWeapon(); + if (useCharges && weapon) { + const ItemType& itemType = Item::items.getItemType(weapon->getID()); + if (itemType.charges) { + int32_t newCount = std::max(0, weapon->getCharges() - 1); + if (newCount <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), newCount); + } + } + } + + lua_pushnumber(L, attackSkill); + lua_pushnumber(L, attackValue); + lua_pushnumber(L, player->getFightMode()); + parameters += 3; + break; + } + + default: { + std::cout << "ValueCallback::getMinMaxValues - unknown callback type" << std::endl; + scriptInterface->resetScriptEnv(); + return; + } + } + + int size0 = lua_gettop(L); + if (lua_pcall(L, parameters, 2, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else { + damage.min = LuaScriptInterface::getNumber(L, -2); + damage.max = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 2); + } + + if ((lua_gettop(L) + parameters + 1) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + scriptInterface->resetScriptEnv(); +} + +//**********************************************************// + +void TileCallback::onTileCombat(Creature* creature, Tile* tile) const +{ + //onTileCombat(creature, pos) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TileCallback::onTileCombat] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + LuaScriptInterface::pushPosition(L, tile->getPosition()); + + scriptInterface->callFunction(2); +} + +//**********************************************************// + +void TargetCallback::onTargetCombat(Creature* creature, Creature* target) const +{ + //onTargetCombat(creature, target) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TargetCallback::onTargetCombat] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + if (!env->setCallbackId(scriptId, scriptInterface)) { + scriptInterface->resetScriptEnv(); + return; + } + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + if (creature) { + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + + if (target) { + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + } else { + lua_pushnil(L); + } + + int size0 = lua_gettop(L); + + if (lua_pcall(L, 2, 0 /*nReturnValues*/, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } + + if ((lua_gettop(L) + 2 /*nParams*/ + 1) != size0) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + scriptInterface->resetScriptEnv(); +} + +//**********************************************************// + +void AreaCombat::clear() +{ + for (const auto& it : areas) { + delete it.second; + } + areas.clear(); +} + +AreaCombat::AreaCombat(const AreaCombat& rhs) +{ + hasExtArea = rhs.hasExtArea; + for (const auto& it : rhs.areas) { + areas[it.first] = new MatrixArea(*it.second); + } +} + +void AreaCombat::getList(const Position& centerPos, const Position& targetPos, std::forward_list& list) const +{ + const MatrixArea* area = getArea(centerPos, targetPos); + if (!area) { + return; + } + + uint32_t centerY, centerX; + area->getCenter(centerY, centerX); + + Position tmpPos(targetPos.x - centerX, targetPos.y - centerY, targetPos.z); + uint32_t cols = area->getCols(); + for (uint32_t y = 0, rows = area->getRows(); y < rows; ++y) { + for (uint32_t x = 0; x < cols; ++x) { + if (area->getValue(y, x) != 0) { + if (g_game.isSightClear(targetPos, tmpPos, true)) { + Tile* tile = g_game.map.getTile(tmpPos); + if (!tile) { + tile = new StaticTile(tmpPos.x, tmpPos.y, tmpPos.z); + g_game.map.setTile(tmpPos, tile); + } + list.push_front(tile); + } + } + tmpPos.x++; + } + tmpPos.x -= cols; + tmpPos.y++; + } +} + +void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const +{ + uint32_t centerY, centerX; + input->getCenter(centerY, centerX); + + if (op == MATRIXOPERATION_COPY) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + (*output)[y][x] = (*input)[y][x]; + } + } + + output->setCenter(centerY, centerX); + } else if (op == MATRIXOPERATION_MIRROR) { + for (uint32_t y = 0; y < input->getRows(); ++y) { + uint32_t rx = 0; + for (int32_t x = input->getCols(); --x >= 0;) { + (*output)[y][rx++] = (*input)[y][x]; + } + } + + output->setCenter(centerY, (input->getRows() - 1) - centerX); + } else if (op == MATRIXOPERATION_FLIP) { + for (uint32_t x = 0; x < input->getCols(); ++x) { + uint32_t ry = 0; + for (int32_t y = input->getRows(); --y >= 0;) { + (*output)[ry++][x] = (*input)[y][x]; + } + } + + output->setCenter((input->getCols() - 1) - centerY, centerX); + } else { + // rotation + int32_t rotateCenterX = (output->getCols() / 2) - 1; + int32_t rotateCenterY = (output->getRows() / 2) - 1; + int32_t angle; + + switch (op) { + case MATRIXOPERATION_ROTATE90: + angle = 90; + break; + + case MATRIXOPERATION_ROTATE180: + angle = 180; + break; + + case MATRIXOPERATION_ROTATE270: + angle = 270; + break; + + default: + angle = 0; + break; + } + + double angleRad = M_PI * angle / 180.0; + + double a = std::cos(angleRad); + double b = -std::sin(angleRad); + double c = std::sin(angleRad); + double d = std::cos(angleRad); + + const uint32_t rows = input->getRows(); + for (uint32_t x = 0, cols = input->getCols(); x < cols; ++x) { + for (uint32_t y = 0; y < rows; ++y) { + //calculate new coordinates using rotation center + int32_t newX = x - centerX; + int32_t newY = y - centerY; + + //perform rotation + int32_t rotatedX = static_cast(round(newX * a + newY * b)); + int32_t rotatedY = static_cast(round(newX * c + newY * d)); + + //write in the output matrix using rotated coordinates + (*output)[rotatedY + rotateCenterY][rotatedX + rotateCenterX] = (*input)[y][x]; + } + } + + output->setCenter(rotateCenterY, rotateCenterX); + } +} + +MatrixArea* AreaCombat::createArea(const std::list& list, uint32_t rows) +{ + uint32_t cols; + if (rows == 0) { + cols = 0; + } else { + cols = list.size() / rows; + } + + MatrixArea* area = new MatrixArea(rows, cols); + + uint32_t x = 0; + uint32_t y = 0; + + for (uint32_t value : list) { + if (value == 1 || value == 3) { + area->setValue(y, x, true); + } + + if (value == 2 || value == 3) { + area->setCenter(y, x); + } + + ++x; + + if (cols == x) { + x = 0; + ++y; + } + } + return area; +} + +void AreaCombat::setupArea(const std::list& list, uint32_t rows) +{ + MatrixArea* area = createArea(list, rows); + + //NORTH + areas[DIRECTION_NORTH] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //SOUTH + MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, southArea, MATRIXOPERATION_ROTATE180); + areas[DIRECTION_SOUTH] = southArea; + + //EAST + MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); + areas[DIRECTION_EAST] = eastArea; + + //WEST + MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, westArea, MATRIXOPERATION_ROTATE270); + areas[DIRECTION_WEST] = westArea; +} + +void AreaCombat::setupArea(int32_t length, int32_t spread) +{ + std::list list; + + uint32_t rows = length; + int32_t cols = 1; + + if (spread != 0) { + cols = ((length - (length % spread)) / spread) * 2 + 1; + } + + int32_t colSpread = cols; + + for (uint32_t y = 1; y <= rows; ++y) { + int32_t mincol = cols - colSpread + 1; + int32_t maxcol = cols - (cols - colSpread); + + for (int32_t x = 1; x <= cols; ++x) { + if (y == rows && x == ((cols - (cols % 2)) / 2) + 1) { + list.push_back(3); + } else if (x >= mincol && x <= maxcol) { + list.push_back(1); + } else { + list.push_back(0); + } + } + + if (spread > 0 && y % spread == 0) { + --colSpread; + } + } + + setupArea(list, rows); +} + +void AreaCombat::setupArea(int32_t radius) +{ + int32_t area[13][13] = { + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {8, 7, 6, 5, 4, 2, 1, 2, 4, 5, 6, 7, 8}, + {0, 8, 6, 5, 4, 3, 2, 3, 4, 5, 6, 8, 0}, + {0, 8, 7, 6, 5, 4, 4, 4, 5, 6, 7, 8, 0}, + {0, 0, 8, 7, 6, 5, 5, 5, 6, 7, 8, 0, 0}, + {0, 0, 0, 8, 7, 6, 6, 6, 7, 8, 0, 0, 0}, + {0, 0, 0, 0, 8, 8, 7, 8, 8, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0} + }; + + std::list list; + + for (auto& row : area) { + for (int cell : row) { + if (cell == 1) { + list.push_back(3); + } else if (cell > 0 && cell <= radius) { + list.push_back(1); + } else { + list.push_back(0); + } + } + } + + setupArea(list, 13); +} + +void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) +{ + if (list.empty()) { + return; + } + + hasExtArea = true; + MatrixArea* area = createArea(list, rows); + + //NORTH-WEST + areas[DIRECTION_NORTHWEST] = area; + + uint32_t maxOutput = std::max(area->getCols(), area->getRows()) * 2; + + //NORTH-EAST + MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, neArea, MATRIXOPERATION_MIRROR); + areas[DIRECTION_NORTHEAST] = neArea; + + //SOUTH-WEST + MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput); + copyArea(area, swArea, MATRIXOPERATION_FLIP); + areas[DIRECTION_SOUTHWEST] = swArea; + + //SOUTH-EAST + MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput); + copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); + areas[DIRECTION_SOUTHEAST] = seArea; +} + +//**********************************************************// + +void MagicField::onStepInField(Creature* creature) +{ + //remove magic walls/wild growth + if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || isBlocking()) { + if (!creature->isInGhostMode()) { + g_game.internalRemoveItem(this, 1); + } + + return; + } + + const ItemType& it = items[getID()]; + if (it.conditionDamage) { + Condition* conditionCopy = it.conditionDamage->clone(); + uint32_t ownerId = getOwner(); + if (ownerId) { + bool harmfulField = true; + + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP || getTile()->hasFlag(TILESTATE_NOPVPZONE)) { + Creature* owner = g_game.getCreatureByID(ownerId); + if (owner) { + if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) { + harmfulField = false; + } + } + } + + Player* targetPlayer = creature->getPlayer(); + if (targetPlayer) { + Player* attackerPlayer = g_game.getPlayerByID(ownerId); + if (attackerPlayer) { + if (Combat::isProtected(attackerPlayer, targetPlayer)) { + harmfulField = false; + } + } + } + + if (!harmfulField || (OTSYS_TIME() - createTime <= 5000) || creature->hasBeenAttacked(ownerId)) { + conditionCopy->setParam(CONDITION_PARAM_OWNER, ownerId); + } + } + + creature->addCondition(conditionCopy); + } +} + +void DamageImpact::handleCreature(Creature* target) +{ + Combat::doCombatHealth(actor, target, damage, params); +} + +void SpeedImpact::handleCreature(Creature* target) +{ + ConditionType_t conditionType = CONDITION_PARALYZE; + if (percent > 0) { + conditionType = CONDITION_HASTE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration)); + condition->setSpeedDelta(percent); + target->addCondition(condition); +} + +void DunkenImpact::handleCreature(Creature* target) +{ + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration); + target->addCondition(condition); +} diff --git a/src/combat.h b/src/combat.h new file mode 100644 index 0000000..9c32e16 --- /dev/null +++ b/src/combat.h @@ -0,0 +1,397 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC +#define FS_COMBAT_H_B02CE79230FC43708699EE91FCC8F7CC + +#include "thing.h" +#include "condition.h" +#include "map.h" +#include "baseevents.h" + +class Condition; +class Creature; +class Item; + +struct Position; + +//for luascript callback +class ValueCallback final : public CallBack +{ + public: + explicit ValueCallback(formulaType_t type): type(type) {} + void getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const; + + protected: + formulaType_t type; +}; + +class TileCallback final : public CallBack +{ + public: + void onTileCombat(Creature* creature, Tile* tile) const; + + protected: + formulaType_t type; +}; + +class TargetCallback final : public CallBack +{ + public: + void onTargetCombat(Creature* creature, Creature* target) const; + + protected: + formulaType_t type; +}; + +struct CombatParams { + std::forward_list> conditionList; + + std::unique_ptr valueCallback; + std::unique_ptr tileCallback; + std::unique_ptr targetCallback; + + uint16_t itemId = 0; + uint16_t decreaseDamage = 0; + uint16_t maximumDecreasedDamage = 0; + + ConditionType_t dispelType = CONDITION_NONE; + CombatType_t combatType = COMBAT_NONE; + + uint8_t impactEffect = CONST_ME_NONE; + uint8_t distanceEffect = CONST_ANI_NONE; + + bool blockedByArmor = false; + bool blockedByShield = false; + bool targetCasterOrTopMost = false; + bool aggressive = true; + bool useCharges = false; +}; + +struct Impact +{ + Creature* actor = nullptr; + + virtual void handleCreature(Creature*) { + // + } +}; + +struct DamageImpact : Impact +{ + CombatParams params; + CombatDamage damage; + + void handleCreature(Creature* target) final; +}; + +struct SpeedImpact : Impact +{ + int32_t percent = 0; + int32_t duration = 0; + + void handleCreature(Creature* target) final; +}; + +struct DunkenImpact : Impact +{ + int32_t duration = 0; + + void handleCreature(Creature* target) final; +}; + +typedef bool (*COMBATFUNC)(Creature*, Creature*, const CombatParams&, CombatDamage*); + +class MatrixArea +{ + public: + MatrixArea(uint32_t rows, uint32_t cols): centerX(0), centerY(0), rows(rows), cols(cols) { + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = 0; + } + } + } + + MatrixArea(const MatrixArea& rhs) { + centerX = rhs.centerX; + centerY = rhs.centerY; + rows = rhs.rows; + cols = rhs.cols; + + data_ = new bool*[rows]; + + for (uint32_t row = 0; row < rows; ++row) { + data_[row] = new bool[cols]; + + for (uint32_t col = 0; col < cols; ++col) { + data_[row][col] = rhs.data_[row][col]; + } + } + } + + ~MatrixArea() { + for (uint32_t row = 0; row < rows; ++row) { + delete[] data_[row]; + } + + delete[] data_; + } + + // non-assignable + MatrixArea& operator=(const MatrixArea&) = delete; + + void setValue(uint32_t row, uint32_t col, bool value) const { + data_[row][col] = value; + } + bool getValue(uint32_t row, uint32_t col) const { + return data_[row][col]; + } + + void setCenter(uint32_t y, uint32_t x) { + centerX = x; + centerY = y; + } + void getCenter(uint32_t& y, uint32_t& x) const { + x = centerX; + y = centerY; + } + + uint32_t getRows() const { + return rows; + } + uint32_t getCols() const { + return cols; + } + + inline const bool* operator[](uint32_t i) const { + return data_[i]; + } + inline bool* operator[](uint32_t i) { + return data_[i]; + } + + protected: + uint32_t centerX; + uint32_t centerY; + + uint32_t rows; + uint32_t cols; + bool** data_; +}; + +class AreaCombat +{ + public: + AreaCombat() = default; + + AreaCombat(const AreaCombat& rhs); + ~AreaCombat() { + clear(); + } + + // non-assignable + AreaCombat& operator=(const AreaCombat&) = delete; + + void getList(const Position& centerPos, const Position& targetPos, std::forward_list& list) const; + + void setupArea(const std::list& list, uint32_t rows); + void setupArea(int32_t length, int32_t spread); + void setupArea(int32_t radius); + void setupExtArea(const std::list& list, uint32_t rows); + void clear(); + + protected: + enum MatrixOperation_t { + MATRIXOPERATION_COPY, + MATRIXOPERATION_MIRROR, + MATRIXOPERATION_FLIP, + MATRIXOPERATION_ROTATE90, + MATRIXOPERATION_ROTATE180, + MATRIXOPERATION_ROTATE270, + }; + + MatrixArea* createArea(const std::list& list, uint32_t rows); + void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const; + + MatrixArea* getArea(const Position& centerPos, const Position& targetPos) const { + int32_t dx = Position::getOffsetX(targetPos, centerPos); + int32_t dy = Position::getOffsetY(targetPos, centerPos); + + Direction dir; + if (dx < 0) { + dir = DIRECTION_WEST; + } else if (dx > 0) { + dir = DIRECTION_EAST; + } else if (dy < 0) { + dir = DIRECTION_NORTH; + } else { + dir = DIRECTION_SOUTH; + } + + if (hasExtArea) { + if (dx < 0 && dy < 0) { + dir = DIRECTION_NORTHWEST; + } else if (dx > 0 && dy < 0) { + dir = DIRECTION_NORTHEAST; + } else if (dx < 0 && dy > 0) { + dir = DIRECTION_SOUTHWEST; + } else if (dx > 0 && dy > 0) { + dir = DIRECTION_SOUTHEAST; + } + } + + auto it = areas.find(dir); + if (it == areas.end()) { + return nullptr; + } + return it->second; + } + + std::map areas; + bool hasExtArea = false; +}; + +class Combat +{ + public: + Combat() = default; + + // non-copyable + Combat(const Combat&) = delete; + Combat& operator=(const Combat&) = delete; + + static int32_t computeDamage(Creature* creature, int32_t strength, int32_t variation); + static int32_t getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode); + + static bool attack(Creature* attacker, Creature* target); + static bool closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode); + static bool rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode); + + static void circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect); + + static void getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill); + + static bool doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + + static void doCombatMana(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doCombatMana(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + + static void doCombatCondition(Creature* caster, Creature* target, const CombatParams& params); + static void doCombatCondition(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params); + + static void doCombatDispel(Creature* caster, Creature* target, const CombatParams& params); + static void doCombatDispel(Creature* caster, const Position& position, const AreaCombat* area, const CombatParams& params); + + static void getCombatArea(const Position& centerPos, const Position& targetPos, const AreaCombat* area, std::forward_list& list); + + static bool isInPvpZone(const Creature* attacker, const Creature* target); + static bool isProtected(const Player* attacker, const Player* target); + static bool isPlayerCombat(const Creature* target); + static CombatType_t ConditionToDamageType(ConditionType_t type); + static ConditionType_t DamageToConditionType(CombatType_t type); + static ReturnValue canTargetCreature(Player* attacker, Creature* target); + static ReturnValue canDoCombat(Creature* caster, Tile* tile, bool aggressive); + static ReturnValue canDoCombat(Creature* attacker, Creature* target); + static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); + + static void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); + + void doCombat(Creature* caster, Creature* target) const; + void doCombat(Creature* caster, const Position& pos) const; + + bool setCallback(CallBackParam_t key); + CallBack* getCallback(CallBackParam_t key); + + bool setParam(CombatParam_t param, uint32_t value); + void setArea(AreaCombat* area) { + this->area.reset(area); + } + bool hasArea() const { + return area != nullptr; + } + void setCondition(const Condition* condition) { + params.conditionList.emplace_front(condition); + } + void setPlayerCombatValues(formulaType_t formulaType, double mina, double minb, double maxa, double maxb); + void postCombatEffects(Creature* caster, const Position& pos) const { + postCombatEffects(caster, pos, params); + } + + protected: + static bool canUseWeapon(Player* player, Item* weapon); + static void postWeaponEffects(Player* player, Item* weapon); + + static void doCombatDefault(Creature* caster, Creature* target, const CombatParams& params); + + static void CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data); + + static bool CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage); + static bool CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + static bool CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); + + static void combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params); + CombatDamage getCombatDamage(Creature* creature) const; + + //configureable + CombatParams params; + + //formula variables + formulaType_t formulaType = COMBAT_FORMULA_UNDEFINED; + double mina = 0.0; + double minb = 0.0; + double maxa = 0.0; + double maxb = 0.0; + + std::unique_ptr area; +}; + +class MagicField final : public Item +{ + public: + explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} + + MagicField* getMagicField() final { + return this; + } + const MagicField* getMagicField() const final { + return this; + } + + bool isReplaceable() const { + return Item::items[getID()].replaceable; + } + CombatType_t getCombatType() const { + const ItemType& it = items[getID()]; + return it.combatType; + } + void onStepInField(Creature* creature); + + private: + int64_t createTime; +}; + +#endif diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..0e42920 --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,328 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "commands.h" +#include "player.h" +#include "npc.h" +#include "game.h" +#include "actions.h" +#include "iologindata.h" +#include "configmanager.h" +#include "spells.h" +#include "movement.h" +#include "globalevent.h" +#include "monster.h" +#include "scheduler.h" + +#include "pugicast.h" + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Monsters g_monsters; +extern TalkActions* g_talkActions; +extern MoveEvents* g_moveEvents; +extern Spells* g_spells; +extern Game g_game; +extern CreatureEvents* g_creatureEvents; +extern GlobalEvents* g_globalEvents; +extern Chat* g_chat; +extern LuaEnvironment g_luaEnvironment; + +s_defcommands Commands::defined_commands[] = { + // TODO: move all commands to talkactions + + //admin commands + {"/reload", &Commands::reloadInfo}, + {"/raid", &Commands::forceRaid}, + + // player commands + {"!sellhouse", &Commands::sellHouse} +}; + +Commands::Commands() +{ + // set up command map + for (auto& command : defined_commands) { + commandMap[command.name] = new Command(command.f, 1, ACCOUNT_TYPE_GOD, true); + } +} + +Commands::~Commands() +{ + for (const auto& it : commandMap) { + delete it.second; + } +} + +bool Commands::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/commands.xml"); + if (!result) { + printXMLError("Error - Commands::loadFromXml", "data/XML/commands.xml", result); + return false; + } + + for (auto commandNode : doc.child("commands").children()) { + pugi::xml_attribute cmdAttribute = commandNode.attribute("cmd"); + if (!cmdAttribute) { + std::cout << "[Warning - Commands::loadFromXml] Missing cmd" << std::endl; + continue; + } + + auto it = commandMap.find(cmdAttribute.as_string()); + if (it == commandMap.end()) { + std::cout << "[Warning - Commands::loadFromXml] Unknown command " << cmdAttribute.as_string() << std::endl; + continue; + } + + Command* command = it->second; + + pugi::xml_attribute groupAttribute = commandNode.attribute("group"); + if (groupAttribute) { + command->groupId = pugi::cast(groupAttribute.value()); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing group for command " << it->first << std::endl; + } + + pugi::xml_attribute acctypeAttribute = commandNode.attribute("acctype"); + if (acctypeAttribute) { + command->accountType = static_cast(pugi::cast(acctypeAttribute.value())); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing acctype for command " << it->first << std::endl; + } + + pugi::xml_attribute logAttribute = commandNode.attribute("log"); + if (logAttribute) { + command->log = booleanString(logAttribute.as_string()); + } else { + std::cout << "[Warning - Commands::loadFromXml] Missing log for command " << it->first << std::endl; + } + g_game.addCommandTag(it->first.front()); + } + return true; +} + +bool Commands::reload() +{ + for (const auto& it : commandMap) { + Command* command = it.second; + command->groupId = 1; + command->accountType = ACCOUNT_TYPE_GOD; + command->log = true; + } + + g_game.resetCommandTag(); + return loadFromXml(); +} + +bool Commands::exeCommand(Player& player, const std::string& cmd) +{ + std::string str_command; + std::string str_param; + + std::string::size_type loc = cmd.find(' ', 0); + if (loc != std::string::npos) { + str_command = std::string(cmd, 0, loc); + str_param = std::string(cmd, (loc + 1), cmd.size() - loc - 1); + } else { + str_command = cmd; + } + + //find command + auto it = commandMap.find(str_command); + if (it == commandMap.end()) { + return false; + } + + Command* command = it->second; + if (command->groupId > player.getGroup()->id || command->accountType > player.getAccountType()) { + if (player.getGroup()->access) { + player.sendTextMessage(MESSAGE_STATUS_SMALL, "You can not execute this command."); + } + + return false; + } + + //execute command + CommandFunc cfunc = command->f; + (this->*cfunc)(player, str_param); + + if (command->log) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, cmd); + + std::ostringstream logFile; + logFile << "data/logs/" << player.getName() << " commands.log"; + std::ofstream out(logFile.str(), std::ios::app); + if (out.is_open()) { + time_t ticks = time(nullptr); + const tm* now = localtime(&ticks); + char buf[32]; + strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M", now); + + out << '[' << buf << "] " << cmd << std::endl; + out.close(); + } + } + return true; +} + +void Commands::reloadInfo(Player& player, const std::string& param) +{ + std::string tmpParam = asLowerCaseString(param); + if (tmpParam == "action" || tmpParam == "actions") { + g_actions->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded actions."); + } else if (tmpParam == "config" || tmpParam == "configuration") { + g_config.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded config."); + } else if (tmpParam == "command" || tmpParam == "commands") { + reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded commands."); + } else if (tmpParam == "creaturescript" || tmpParam == "creaturescripts") { + g_creatureEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded creature scripts."); + } else if (tmpParam == "monster" || tmpParam == "monsters") { + g_monsters.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded monsters."); + } else if (tmpParam == "move" || tmpParam == "movement" || tmpParam == "movements" + || tmpParam == "moveevents" || tmpParam == "moveevent") { + g_moveEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded movements."); + } else if (tmpParam == "npc" || tmpParam == "npcs") { + Npcs::reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded npcs."); + } else if (tmpParam == "raid" || tmpParam == "raids") { + g_game.raids.reload(); + g_game.raids.startup(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded raids."); + } else if (tmpParam == "spell" || tmpParam == "spells") { + g_spells->reload(); + g_monsters.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded spells."); + } else if (tmpParam == "talk" || tmpParam == "talkaction" || tmpParam == "talkactions") { + g_talkActions->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded talk actions."); + } else if (tmpParam == "items") { + Item::items.reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded items."); + } else if (tmpParam == "globalevents" || tmpParam == "globalevent") { + g_globalEvents->reload(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded globalevents."); + } else if (tmpParam == "chat" || tmpParam == "channel" || tmpParam == "chatchannels") { + g_chat->load(); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded chatchannels."); + } else if (tmpParam == "global") { + g_luaEnvironment.loadFile("data/global.lua"); + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reloaded global.lua."); + } else { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Reload type not found."); + } + lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); +} + +void Commands::sellHouse(Player& player, const std::string& param) +{ + Player* tradePartner = g_game.getPlayerByName(param); + if (!tradePartner || tradePartner == &player) { + player.sendCancelMessage("Trade player not found."); + return; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player.getPosition())) { + player.sendCancelMessage("Trade player is too far away."); + return; + } + + if (!tradePartner->isPremium()) { + player.sendCancelMessage("Trade player does not have a premium account."); + return; + } + + HouseTile* houseTile = dynamic_cast(player.getTile()); + if (!houseTile) { + player.sendCancelMessage("You must stand in your house to initiate the trade."); + return; + } + + House* house = houseTile->getHouse(); + if (!house || house->getOwner() != player.getGUID()) { + player.sendCancelMessage("You don't own this house."); + return; + } + + if (g_game.map.houses.getHouseByPlayerId(tradePartner->getGUID())) { + player.sendCancelMessage("Trade player already owns a house."); + return; + } + + if (IOLoginData::hasBiddedOnHouse(tradePartner->getGUID())) { + player.sendCancelMessage("Trade player is currently the highest bidder of an auctioned house."); + return; + } + + Item* transferItem = house->getTransferItem(); + if (!transferItem) { + player.sendCancelMessage("You can not trade this house."); + return; + } + + transferItem->getParent()->setParent(&player); + + if (!g_game.internalStartTrade(&player, tradePartner, transferItem)) { + house->resetTransferItem(); + } +} + +void Commands::forceRaid(Player& player, const std::string& param) +{ + Raid* raid = g_game.raids.getRaidByName(param); + if (!raid || !raid->isLoaded()) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "No such raid exists."); + return; + } + + if (g_game.raids.getRunning()) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Another raid is already being executed."); + return; + } + + g_game.raids.setRunning(raid); + + RaidEvent* event = raid->getNextRaidEvent(); + if (!event) { + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "The raid does not contain any data."); + return; + } + + raid->setState(RAIDSTATE_EXECUTING); + + uint32_t ticks = event->getDelay(); + if (ticks > 0) { + g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, raid, event))); + } else { + g_dispatcher.addTask(createTask(std::bind(&Raid::executeRaidEvent, raid, event))); + } + + player.sendTextMessage(MESSAGE_STATUS_CONSOLE_BLUE, "Raid started."); +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..2be9ac1 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,74 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_COMMANDS_H_C95A575CCADF434699D26CD042690970 +#define FS_COMMANDS_H_C95A575CCADF434699D26CD042690970 + +#include "enums.h" + +class Player; + +struct Command; +struct s_defcommands; + +class Commands +{ + public: + Commands(); + ~Commands(); + + // non-copyable + Commands(const Commands&) = delete; + Commands& operator=(const Commands&) = delete; + + bool loadFromXml(); + bool reload(); + + bool exeCommand(Player& player, const std::string& cmd); + + protected: + //commands + void reloadInfo(Player& player, const std::string& param); + void sellHouse(Player& player, const std::string& param); + void forceRaid(Player& player, const std::string& param); + + //table of commands + static s_defcommands defined_commands[]; + + std::map commandMap; +}; + +typedef void (Commands::*CommandFunc)(Player&, const std::string&); + +struct Command { + Command(CommandFunc f, uint32_t groupId, AccountType_t accountType, bool log) + : f(f), groupId(groupId), accountType(accountType), log(log) {} + + CommandFunc f; + uint32_t groupId; + AccountType_t accountType; + bool log; +}; + +struct s_defcommands { + const char* name; + CommandFunc f; +}; + +#endif diff --git a/src/condition.cpp b/src/condition.cpp new file mode 100644 index 0000000..053c1ea --- /dev/null +++ b/src/condition.cpp @@ -0,0 +1,1297 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "condition.h" +#include "game.h" + +extern Game g_game; + +bool Condition::setParam(ConditionParam_t param, int32_t value) +{ + switch (param) { + case CONDITION_PARAM_TICKS: { + ticks = value; + return true; + } + + case CONDITION_PARAM_SUBID: { + subId = value; + return true; + } + + default: { + return false; + } + } +} + +bool Condition::unserialize(PropStream& propStream) +{ + uint8_t attr_type; + while (propStream.read(attr_type) && attr_type != CONDITIONATTR_END) { + if (!unserializeProp(static_cast(attr_type), propStream)) { + return false; + } + } + return true; +} + +bool Condition::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + switch (attr) { + case CONDITIONATTR_TYPE: { + int32_t value; + if (!propStream.read(value)) { + return false; + } + + conditionType = static_cast(value); + return true; + } + + case CONDITIONATTR_ID: { + int32_t value; + if (!propStream.read(value)) { + return false; + } + + id = static_cast(value); + return true; + } + + case CONDITIONATTR_TICKS: { + return propStream.read(ticks); + } + + case CONDITIONATTR_SUBID: { + return propStream.read(subId); + } + + case CONDITIONATTR_END: + return true; + + default: + return false; + } +} + +void Condition::serialize(PropWriteStream& propWriteStream) +{ + propWriteStream.write(CONDITIONATTR_TYPE); + propWriteStream.write(conditionType); + + propWriteStream.write(CONDITIONATTR_ID); + propWriteStream.write(id); + + propWriteStream.write(CONDITIONATTR_TICKS); + propWriteStream.write(ticks); + + propWriteStream.write(CONDITIONATTR_SUBID); + propWriteStream.write(subId); +} + +void Condition::setTicks(int32_t newTicks) +{ + ticks = newTicks; + endTime = ticks + OTSYS_TIME(); +} + +bool Condition::executeCondition(Creature*, int32_t interval) +{ + if (ticks == -1) { + return true; + } + + //Not using set ticks here since it would reset endTime + ticks = std::max(0, ticks - interval); + return getEndTime() >= OTSYS_TIME(); +} + +Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, uint32_t subId/* = 0*/) +{ + switch (type) { + case CONDITION_POISON: + case CONDITION_FIRE: + case CONDITION_ENERGY: + return new ConditionDamage(id, type, subId); + + case CONDITION_HASTE: + case CONDITION_PARALYZE: + return new ConditionSpeed(id, type, ticks, subId, param); + + case CONDITION_INVISIBLE: + return new ConditionInvisible(id, type, ticks, subId); + + case CONDITION_OUTFIT: + return new ConditionOutfit(id, type, ticks, subId); + + case CONDITION_LIGHT: + return new ConditionLight(id, type, ticks, subId, param & 0xFF, (param & 0xFF00) >> 8); + + case CONDITION_REGENERATION: + return new ConditionRegeneration(id, type, ticks, subId); + + case CONDITION_SOUL: + return new ConditionSoul(id, type, ticks, subId); + + case CONDITION_ATTRIBUTES: + return new ConditionAttributes(id, type, ticks, subId); + + case CONDITION_INFIGHT: + case CONDITION_DRUNK: + case CONDITION_EXHAUST: + case CONDITION_MUTED: + case CONDITION_CHANNELMUTEDTICKS: + case CONDITION_YELLTICKS: + case CONDITION_PACIFIED: + case CONDITION_MANASHIELD: + case CONDITION_AGGRESSIVE: + return new ConditionGeneric(id, type, ticks, subId); + + default: + return nullptr; + } +} + +Condition* Condition::createCondition(PropStream& propStream) +{ + uint8_t attr; + if (!propStream.read(attr) || attr != CONDITIONATTR_TYPE) { + return nullptr; + } + + uint32_t type; + if (!propStream.read(type)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_ID) { + return nullptr; + } + + uint32_t id; + if (!propStream.read(id)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_TICKS) { + return nullptr; + } + + uint32_t ticks; + if (!propStream.read(ticks)) { + return nullptr; + } + + if (!propStream.read(attr) || attr != CONDITIONATTR_SUBID) { + return nullptr; + } + + uint32_t subId; + if (!propStream.read(subId)) { + return nullptr; + } + + return createCondition(static_cast(id), static_cast(type), ticks, 0, subId); +} + +bool Condition::startCondition(Creature*) +{ + if (ticks > 0) { + endTime = ticks + OTSYS_TIME(); + } + return true; +} + +bool Condition::isPersistent() const +{ + if (ticks == -1) { + return false; + } + + if (!(id == CONDITIONID_DEFAULT || id == CONDITIONID_COMBAT)) { + return false; + } + + return true; +} + +uint32_t Condition::getIcons() const +{ + return 0; +} + +bool Condition::updateCondition(const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return false; + } + + if (ticks == -1 && addCondition->getTicks() > 0) { + return false; + } + + if (addCondition->getTicks() >= 0 && getEndTime() > (OTSYS_TIME() + addCondition->getTicks())) { + return false; + } + + return true; +} + +bool ConditionGeneric::startCondition(Creature* creature) +{ + return Condition::startCondition(creature); +} + +bool ConditionGeneric::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionGeneric::endCondition(Creature*) +{ + // +} + +void ConditionGeneric::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + } +} + +uint32_t ConditionGeneric::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + + switch (conditionType) { + case CONDITION_MANASHIELD: + icons |= ICON_MANASHIELD; + break; + + case CONDITION_INFIGHT: + icons |= ICON_SWORDS; + break; + + case CONDITION_DRUNK: + icons |= ICON_DRUNK; + break; + + default: + break; + } + + return icons; +} + +void ConditionAttributes::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionAttributes& conditionAttrs = static_cast(*addCondition); + //Remove the old condition + endCondition(creature); + + //Apply the new one + memcpy(skills, conditionAttrs.skills, sizeof(skills)); + memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); + memcpy(stats, conditionAttrs.stats, sizeof(stats)); + memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + } +} + +bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SKILLS) { + return propStream.read(skills[currentSkill++]); + } else if (attr == CONDITIONATTR_STATS) { + return propStream.read(stats[currentStat++]); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionAttributes::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + propWriteStream.write(CONDITIONATTR_SKILLS); + propWriteStream.write(skills[i]); + } + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + propWriteStream.write(CONDITIONATTR_STATS); + propWriteStream.write(stats[i]); + } +} + +bool ConditionAttributes::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (Player* player = creature->getPlayer()) { + updatePercentSkills(player); + updateSkills(player); + updatePercentStats(player); + updateStats(player); + } + + return true; +} + +void ConditionAttributes::updatePercentStats(Player* player) +{ + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (statsPercent[i] == 0) { + continue; + } + + switch (i) { + case STAT_MAXHITPOINTS: + stats[i] = static_cast(player->getMaxHealth() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAXMANAPOINTS: + stats[i] = static_cast(player->getMaxMana() * ((statsPercent[i] - 100) / 100.f)); + break; + + case STAT_MAGICPOINTS: + stats[i] = static_cast(player->getMagicLevel() * ((statsPercent[i] - 100) / 100.f)); + break; + } + } +} + +void ConditionAttributes::updateStats(Player* player) +{ + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats(static_cast(i), stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } +} + +void ConditionAttributes::updatePercentSkills(Player* player) +{ + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skillsPercent[i] == 0) { + continue; + } + + int32_t unmodifiedSkill = player->getBaseSkill(i); + skills[i] = static_cast(unmodifiedSkill * ((skillsPercent[i] - 100) / 100.f)); + } +} + +void ConditionAttributes::updateSkills(Player* player) +{ + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } +} + +bool ConditionAttributes::executeCondition(Creature* creature, int32_t interval) +{ + return ConditionGeneric::executeCondition(creature, interval); +} + +void ConditionAttributes::endCondition(Creature* creature) +{ + Player* player = creature->getPlayer(); + if (player) { + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (skills[i] || skillsPercent[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), -skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + bool needUpdateStats = false; + + for (int32_t i = STAT_FIRST; i <= STAT_LAST; ++i) { + if (stats[i]) { + needUpdateStats = true; + player->setVarStats(static_cast(i), -stats[i]); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + } +} + +bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_SKILL_MELEE: { + skills[SKILL_CLUB] = value; + skills[SKILL_AXE] = value; + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_MELEEPERCENT: { + skillsPercent[SKILL_CLUB] = value; + skillsPercent[SKILL_AXE] = value; + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FIST: { + skills[SKILL_FIST] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISTPERCENT: { + skillsPercent[SKILL_FIST] = value; + return true; + } + + case CONDITION_PARAM_SKILL_CLUB: { + skills[SKILL_CLUB] = value; + return true; + } + + case CONDITION_PARAM_SKILL_CLUBPERCENT: { + skillsPercent[SKILL_CLUB] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SWORD: { + skills[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SWORDPERCENT: { + skillsPercent[SKILL_SWORD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_AXE: { + skills[SKILL_AXE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_AXEPERCENT: { + skillsPercent[SKILL_AXE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_DISTANCE: { + skills[SKILL_DISTANCE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_DISTANCEPERCENT: { + skillsPercent[SKILL_DISTANCE] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SHIELD: { + skills[SKILL_SHIELD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_SHIELDPERCENT: { + skillsPercent[SKILL_SHIELD] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISHING: { + skills[SKILL_FISHING] = value; + return true; + } + + case CONDITION_PARAM_SKILL_FISHINGPERCENT: { + skillsPercent[SKILL_FISHING] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXHITPOINTS: { + stats[STAT_MAXHITPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXMANAPOINTS: { + stats[STAT_MAXMANAPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAGICPOINTS: { + stats[STAT_MAGICPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT: { + statsPercent[STAT_MAXHITPOINTS] = std::max(0, value); + return true; + } + + case CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT: { + statsPercent[STAT_MAXMANAPOINTS] = std::max(0, value); + return true; + } + + case CONDITION_PARAM_STAT_MAGICPOINTSPERCENT: { + statsPercent[STAT_MAGICPOINTS] = std::max(0, value); + return true; + } + + default: + return ret; + } +} + +void ConditionRegeneration::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionRegeneration& conditionRegen = static_cast(*addCondition); + + healthTicks = conditionRegen.healthTicks; + manaTicks = conditionRegen.manaTicks; + + healthGain = conditionRegen.healthGain; + manaGain = conditionRegen.manaGain; + } +} + +bool ConditionRegeneration::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_HEALTHTICKS) { + return propStream.read(healthTicks); + } else if (attr == CONDITIONATTR_HEALTHGAIN) { + return propStream.read(healthGain); + } else if (attr == CONDITIONATTR_MANATICKS) { + return propStream.read(manaTicks); + } else if (attr == CONDITIONATTR_MANAGAIN) { + return propStream.read(manaGain); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionRegeneration::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_HEALTHTICKS); + propWriteStream.write(healthTicks); + + propWriteStream.write(CONDITIONATTR_HEALTHGAIN); + propWriteStream.write(healthGain); + + propWriteStream.write(CONDITIONATTR_MANATICKS); + propWriteStream.write(manaTicks); + + propWriteStream.write(CONDITIONATTR_MANAGAIN); + propWriteStream.write(manaGain); +} + +bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interval) +{ + internalHealthTicks += interval; + internalManaTicks += interval; + + if (creature->getZone() != ZONE_PROTECTION) { + if (internalHealthTicks >= healthTicks) { + internalHealthTicks = 0; + + creature->changeHealth(healthGain); + } + + if (internalManaTicks >= manaTicks) { + internalManaTicks = 0; + creature->changeMana(manaGain); + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_HEALTHGAIN: + healthGain = value; + return true; + + case CONDITION_PARAM_HEALTHTICKS: + healthTicks = value; + return true; + + case CONDITION_PARAM_MANAGAIN: + manaGain = value; + return true; + + case CONDITION_PARAM_MANATICKS: + manaTicks = value; + return true; + + default: + return ret; + } +} + +void ConditionSoul::addCondition(Creature*, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionSoul& conditionSoul = static_cast(*addCondition); + + soulTicks = conditionSoul.soulTicks; + soulGain = conditionSoul.soulGain; + } +} + +bool ConditionSoul::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SOULGAIN) { + return propStream.read(soulGain); + } else if (attr == CONDITIONATTR_SOULTICKS) { + return propStream.read(soulTicks); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionSoul::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_SOULGAIN); + propWriteStream.write(soulGain); + + propWriteStream.write(CONDITIONATTR_SOULTICKS); + propWriteStream.write(soulTicks); +} + +bool ConditionSoul::executeCondition(Creature* creature, int32_t interval) +{ + internalSoulTicks += interval; + + if (Player* player = creature->getPlayer()) { + if (player->getZone() != ZONE_PROTECTION) { + if (internalSoulTicks >= soulTicks) { + internalSoulTicks = 0; + player->changeSoul(soulGain); + } + } + } + + return ConditionGeneric::executeCondition(creature, interval); +} + +bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = ConditionGeneric::setParam(param, value); + switch (param) { + case CONDITION_PARAM_SOULGAIN: + soulGain = value; + return true; + + case CONDITION_PARAM_SOULTICKS: + soulTicks = value; + return true; + + default: + return ret; + } +} + +bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + + switch (param) { + case CONDITION_PARAM_OWNER: + owner = value; + return true; + + case CONDITION_PARAM_CYCLE: + cycle = value; + return true; + + case CONDITION_PARAM_COUNT: + count = value; + return true; + + case CONDITION_PARAM_MAX_COUNT: + max_count = value; + return true; + + case CONDITION_PARAM_HIT_DAMAGE: + hit_damage = value; + return true; + + default: + return false; + } +} + +bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_OWNER) { + return propStream.skip(4); + } else if (attr == CONDITIONATTR_CYCLE) { + if (!propStream.read(cycle)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_COUNT) { + if (!propStream.read(count)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_MAX_COUNT) { + if (!propStream.read(max_count)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_FACTOR_PERCENT) { + if (!propStream.read(factor_percent)) { + return false; + } + + return true; + } + + return Condition::unserializeProp(attr, propStream); +} + +void ConditionDamage::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_CYCLE); + propWriteStream.write(cycle); + + propWriteStream.write(CONDITIONATTR_COUNT); + propWriteStream.write(count); + + propWriteStream.write(CONDITIONATTR_MAX_COUNT); + propWriteStream.write(max_count); + + propWriteStream.write(CONDITIONATTR_FACTOR_PERCENT); + propWriteStream.write(factor_percent); +} + +bool ConditionDamage::updateCondition(const Condition* addCondition) +{ + const ConditionDamage& conditionDamage = static_cast(*addCondition); + return conditionDamage.getTotalDamage() >= getTotalDamage(); +} + +bool ConditionDamage::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + creature->onAttacked(); + + setParam(CONDITION_PARAM_TICKINTERVAL, 1000); + + if (factor_percent == -1) { + factor_percent = 50; + } + + if (factor_percent <= 9) { + factor_percent = 10; + } + + if (factor_percent >= 1001) { + factor_percent = 1000; + } + + if (hit_damage) { + doDamage(creature, -hit_damage); + } + + return true; +} + +bool ConditionDamage::executeCondition(Creature* creature, int32_t) +{ + if (conditionType == CONDITION_FIRE) { + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -10); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_POISON) { + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + int32_t f = factor_percent * r_cycle / 1000; + if (!f) { + f = 2 * (r_cycle > 0) - 1; + } + + cycle = r_cycle - f; + doDamage(creature, -f); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_ENERGY) { + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -25); + } else { + --count; + } + } else { + return false; + } + } + + return true; +} + +bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) +{ + if (creature->isSuppress(getType())) { + return true; + } + + CombatDamage damage; + damage.value = healthChange; + damage.type = Combat::ConditionToDamageType(conditionType); + + Creature* attacker = g_game.getCreatureByID(owner); + + if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature) != RETURNVALUE_NOERROR) { + if (!creature->isInGhostMode()) { + g_game.addMagicEffect(creature->getPosition(), CONST_ME_POFF); + } + return false; + } + + if (g_game.combatBlockHit(damage, attacker, creature, false, false, true)) { + return false; + } + return g_game.combatChangeHealth(attacker, creature, damage); +} + +void ConditionDamage::endCondition(Creature*) +{ + // +} + +void ConditionDamage::addCondition(Creature* creature, const Condition* addCondition) +{ + if (addCondition->getType() != conditionType) { + return; + } + + const ConditionDamage& conditionDamage = static_cast(*addCondition); + + if (hit_damage) { + doDamage(creature, -conditionDamage.hit_damage); + } + + if (!updateCondition(addCondition)) { + return; + } + + owner = conditionDamage.owner; + cycle = conditionDamage.cycle; + count = conditionDamage.count; + max_count = conditionDamage.max_count; +} + +int32_t ConditionDamage::getTotalDamage() const +{ + return cycle; +} + +uint32_t ConditionDamage::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + switch (conditionType) { + case CONDITION_FIRE: + icons |= ICON_BURN; + break; + + case CONDITION_ENERGY: + icons |= ICON_ENERGY; + break; + + case CONDITION_POISON: + icons |= ICON_POISON; + break; + + default: + break; + } + return icons; +} + +bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_SPEEDDELTA) { + return propStream.read(speedDelta); + } else if (attr == CONDITIONATTR_APPLIEDSPEEDDELTA) { + return propStream.read(appliedSpeedDelta); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionSpeed::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_SPEEDDELTA); + propWriteStream.write(speedDelta); + + propWriteStream.write(CONDITIONATTR_APPLIEDSPEEDDELTA); + propWriteStream.write(appliedSpeedDelta); +} + +bool ConditionSpeed::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (appliedSpeedDelta == 0) { + speedDelta = normal_random(-variation, variation) + speedDelta; + + if (speedDelta >= -100) { + speedDelta = static_cast(creature->getBaseSpeed()) * speedDelta / 100; + } else { + speedDelta = -20 - creature->getBaseSpeed(); + } + + appliedSpeedDelta = speedDelta; + } else { + speedDelta = appliedSpeedDelta; + } + + g_game.changeSpeed(creature, speedDelta); + return true; +} + +bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionSpeed::endCondition(Creature* creature) +{ + g_game.changeSpeed(creature, -appliedSpeedDelta); +} + +void ConditionSpeed::addCondition(Creature* creature, const Condition* addCondition) +{ + if (conditionType != addCondition->getType()) { + return; + } + + if (ticks == -1 && addCondition->getTicks() > 0) { + return; + } + + const ConditionSpeed& conditionSpeed = static_cast(*addCondition); + + int32_t newVariation = conditionSpeed.variation; + int32_t newSpeedDelta = conditionSpeed.speedDelta; + + newSpeedDelta = normal_random(-newVariation, newVariation) + newSpeedDelta; + + // update ticks + setTicks(addCondition->getTicks()); + + if (newSpeedDelta >= -100) { + newSpeedDelta = static_cast(creature->getBaseSpeed()) * newSpeedDelta / 100; + } else { + newSpeedDelta = -20 - creature->getBaseSpeed(); + } + + creature->setSpeed(-appliedSpeedDelta); + + appliedSpeedDelta = newSpeedDelta; + speedDelta = newSpeedDelta; + + g_game.changeSpeed(creature, newSpeedDelta); +} + +uint32_t ConditionSpeed::getIcons() const +{ + uint32_t icons = Condition::getIcons(); + switch (conditionType) { + case CONDITION_HASTE: + icons |= ICON_HASTE; + break; + + case CONDITION_PARALYZE: + icons |= ICON_PARALYZE; + break; + + default: + break; + } + return icons; +} + +bool ConditionInvisible::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + g_game.internalCreatureChangeVisible(creature, false); + return true; +} + +void ConditionInvisible::endCondition(Creature* creature) +{ + if (!creature->isInvisible()) { + g_game.internalCreatureChangeVisible(creature, true); + } +} + +void ConditionOutfit::setOutfit(const Outfit_t& outfit) +{ + this->outfit = outfit; +} + +bool ConditionOutfit::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_OUTFIT) { + return propStream.read(outfit); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionOutfit::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + propWriteStream.write(CONDITIONATTR_OUTFIT); + propWriteStream.write(outfit); +} + +bool ConditionOutfit::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + g_game.internalCreatureChangeOutfit(creature, outfit); + return true; +} + +bool ConditionOutfit::executeCondition(Creature* creature, int32_t interval) +{ + return Condition::executeCondition(creature, interval); +} + +void ConditionOutfit::endCondition(Creature* creature) +{ + g_game.internalCreatureChangeOutfit(creature, creature->getDefaultOutfit()); +} + +void ConditionOutfit::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionOutfit& conditionOutfit = static_cast(*addCondition); + outfit = conditionOutfit.outfit; + + g_game.internalCreatureChangeOutfit(creature, outfit); + } +} + +bool ConditionLight::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + internalLightTicks = 0; + lightChangeInterval = ticks / lightInfo.level; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + return true; +} + +bool ConditionLight::executeCondition(Creature* creature, int32_t interval) +{ + internalLightTicks += interval; + + if (internalLightTicks >= lightChangeInterval) { + internalLightTicks = 0; + LightInfo creatureLight; + creature->getCreatureLight(creatureLight); + + if (creatureLight.level > 0) { + --creatureLight.level; + creature->setCreatureLight(creatureLight); + g_game.changeLight(creature); + } + } + + return Condition::executeCondition(creature, interval); +} + +void ConditionLight::endCondition(Creature* creature) +{ + creature->setNormalCreatureLight(); + g_game.changeLight(creature); +} + +void ConditionLight::addCondition(Creature* creature, const Condition* addCondition) +{ + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); + + const ConditionLight& conditionLight = static_cast(*addCondition); + lightInfo.level = conditionLight.lightInfo.level; + lightInfo.color = conditionLight.lightInfo.color; + lightChangeInterval = ticks / lightInfo.level; + internalLightTicks = 0; + creature->setCreatureLight(lightInfo); + g_game.changeLight(creature); + } +} + +bool ConditionLight::setParam(ConditionParam_t param, int32_t value) +{ + bool ret = Condition::setParam(param, value); + if (ret) { + return false; + } + + switch (param) { + case CONDITION_PARAM_LIGHT_LEVEL: + lightInfo.level = value; + return true; + + case CONDITION_PARAM_LIGHT_COLOR: + lightInfo.color = value; + return true; + + default: + return false; + } +} + +bool ConditionLight::unserializeProp(ConditionAttr_t attr, PropStream& propStream) +{ + if (attr == CONDITIONATTR_LIGHTCOLOR) { + uint32_t value; + if (!propStream.read(value)) { + return false; + } + + lightInfo.color = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTLEVEL) { + uint32_t value; + if (!propStream.read(value)) { + return false; + } + + lightInfo.level = value; + return true; + } else if (attr == CONDITIONATTR_LIGHTTICKS) { + return propStream.read(internalLightTicks); + } else if (attr == CONDITIONATTR_LIGHTINTERVAL) { + return propStream.read(lightChangeInterval); + } + return Condition::unserializeProp(attr, propStream); +} + +void ConditionLight::serialize(PropWriteStream& propWriteStream) +{ + Condition::serialize(propWriteStream); + + // TODO: color and level could be serialized as 8-bit if we can retain backwards + // compatibility, but perhaps we should keep it like this in case they increase + // in the future... + propWriteStream.write(CONDITIONATTR_LIGHTCOLOR); + propWriteStream.write(lightInfo.color); + + propWriteStream.write(CONDITIONATTR_LIGHTLEVEL); + propWriteStream.write(lightInfo.level); + + propWriteStream.write(CONDITIONATTR_LIGHTTICKS); + propWriteStream.write(internalLightTicks); + + propWriteStream.write(CONDITIONATTR_LIGHTINTERVAL); + propWriteStream.write(lightChangeInterval); +} diff --git a/src/condition.h b/src/condition.h new file mode 100644 index 0000000..efee824 --- /dev/null +++ b/src/condition.h @@ -0,0 +1,377 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 +#define FS_CONDITION_H_F92FF8BDDD5B4EA59E2B1BB5C9C0A086 + +#include "fileloader.h" +#include "enums.h" + +class Creature; +class Player; +class PropStream; + +enum ConditionAttr_t { + CONDITIONATTR_TYPE = 1, + CONDITIONATTR_ID, + CONDITIONATTR_TICKS, + CONDITIONATTR_HEALTHTICKS, + CONDITIONATTR_HEALTHGAIN, + CONDITIONATTR_MANATICKS, + CONDITIONATTR_MANAGAIN, + CONDITIONATTR_OWNER, + CONDITIONATTR_CYCLE, + CONDITIONATTR_COUNT, + CONDITIONATTR_MAX_COUNT, + CONDITIONATTR_FACTOR_PERCENT, + CONDITIONATTR_SPEEDDELTA, + CONDITIONATTR_APPLIEDSPEEDDELTA, + CONDITIONATTR_FORMULA_MINA, + CONDITIONATTR_FORMULA_MINB, + CONDITIONATTR_FORMULA_MAXA, + CONDITIONATTR_FORMULA_MAXB, + CONDITIONATTR_LIGHTCOLOR, + CONDITIONATTR_LIGHTLEVEL, + CONDITIONATTR_LIGHTTICKS, + CONDITIONATTR_LIGHTINTERVAL, + CONDITIONATTR_SOULTICKS, + CONDITIONATTR_SOULGAIN, + CONDITIONATTR_SKILLS, + CONDITIONATTR_STATS, + CONDITIONATTR_OUTFIT, + CONDITIONATTR_SUBID, + + //reserved for serialization + CONDITIONATTR_END = 254, +}; + +struct IntervalInfo { + int32_t timeLeft; + int32_t value; + int32_t interval; +}; + +class Condition +{ + public: + Condition() = default; + Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + endTime(ticks == -1 ? std::numeric_limits::max() : 0), + subId(subId), ticks(ticks), conditionType(type), id(id) {} + virtual ~Condition() = default; + + virtual bool startCondition(Creature* creature); + virtual bool executeCondition(Creature* creature, int32_t interval); + virtual void endCondition(Creature* creature) = 0; + virtual void addCondition(Creature* creature, const Condition* condition) = 0; + virtual uint32_t getIcons() const; + ConditionId_t getId() const { + return id; + } + uint32_t getSubId() const { + return subId; + } + + virtual Condition* clone() const = 0; + + ConditionType_t getType() const { + return conditionType; + } + int64_t getEndTime() const { + return endTime; + } + int32_t getTicks() const { + return ticks; + } + void setTicks(int32_t newTicks); + + static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, uint32_t subId = 0); + static Condition* createCondition(PropStream& propStream); + + virtual bool setParam(ConditionParam_t param, int32_t value); + + //serialization + bool unserialize(PropStream& propStream); + virtual void serialize(PropWriteStream& propWriteStream); + virtual bool unserializeProp(ConditionAttr_t attr, PropStream& propStream); + + bool isPersistent() const; + + protected: + int64_t endTime; + uint32_t subId; + int32_t ticks; + ConditionType_t conditionType; + ConditionId_t id; + + virtual bool updateCondition(const Condition* addCondition); +}; + +class ConditionGeneric : public Condition +{ + public: + ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0): + Condition(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) override; + bool executeCondition(Creature* creature, int32_t interval) override; + void endCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + uint32_t getIcons() const override; + + ConditionGeneric* clone() const override { + return new ConditionGeneric(*this); + } +}; + +class ConditionAttributes final : public ConditionGeneric +{ + public: + ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionAttributes* clone() const final { + return new ConditionAttributes(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + int32_t skills[SKILL_LAST + 1] = {}; + int32_t skillsPercent[SKILL_LAST + 1] = {}; + int32_t stats[STAT_LAST + 1] = {}; + int32_t statsPercent[STAT_LAST + 1] = {}; + int32_t currentSkill = 0; + int32_t currentStat = 0; + + void updatePercentStats(Player* player); + void updateStats(Player* player); + void updatePercentSkills(Player* player); + void updateSkills(Player* player); +}; + +class ConditionRegeneration final : public ConditionGeneric +{ + public: + ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0): + ConditionGeneric(id, type, ticks, subId) {} + + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionRegeneration* clone() const final { + return new ConditionRegeneration(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + uint32_t internalHealthTicks = 0; + uint32_t internalManaTicks = 0; + + uint32_t healthTicks = 1000; + uint32_t manaTicks = 1000; + uint32_t healthGain = 0; + uint32_t manaGain = 0; +}; + +class ConditionSoul final : public ConditionGeneric +{ + public: + ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; + + bool setParam(ConditionParam_t param, int32_t value) final; + + ConditionSoul* clone() const final { + return new ConditionSoul(*this); + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + uint32_t internalSoulTicks = 0; + uint32_t soulTicks = 0; + uint32_t soulGain = 0; +}; + +class ConditionInvisible final : public ConditionGeneric +{ + public: + ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + void endCondition(Creature* creature) final; + + ConditionInvisible* clone() const final { + return new ConditionInvisible(*this); + } +}; + +class ConditionDamage final : public Condition +{ + public: + ConditionDamage() = default; + ConditionDamage(ConditionId_t id, ConditionType_t type, uint32_t subId = 0) : + Condition(id, type, 0, subId) { + if (type == CONDITION_POISON) { + count = max_count = 3; + } else if (type == CONDITION_FIRE) { + count = max_count = 8; + } else if (type == CONDITION_ENERGY) { + count = max_count = 10; + } + } + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + uint32_t getIcons() const final; + + ConditionDamage* clone() const final { + return new ConditionDamage(*this); + } + + bool setParam(ConditionParam_t param, int32_t value) final; + + int32_t getTotalDamage() const; + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + protected: + int32_t cycle = 0; + int32_t count = 0; + int32_t max_count = 0; + int32_t factor_percent = -1; + int32_t hit_damage = 0; + + uint32_t owner = 0; + + bool doDamage(Creature* creature, int32_t healthChange); + + bool updateCondition(const Condition* addCondition) final; +}; + +class ConditionSpeed final : public Condition +{ + public: + ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, int32_t changeSpeed) : + Condition(id, type, ticks, subId), speedDelta(changeSpeed) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + uint32_t getIcons() const final; + + ConditionSpeed* clone() const final { + return new ConditionSpeed(*this); + } + + void setVariation(int32_t newVariation) { + variation = newVariation; + } + void setSpeedDelta(int32_t newSpeedDelta) { + speedDelta = newSpeedDelta; + } + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + int32_t appliedSpeedDelta = 0; + int32_t speedDelta = 0; + int32_t variation = 0; +}; + +class ConditionOutfit final : public Condition +{ + public: + ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + Condition(id, type, ticks, subId) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* condition) final; + + ConditionOutfit* clone() const final { + return new ConditionOutfit(*this); + } + + void setOutfit(const Outfit_t& outfit); + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + Outfit_t outfit; +}; + +class ConditionLight final : public Condition +{ + public: + ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor) : + Condition(id, type, ticks, subId), lightInfo(lightlevel, lightcolor) {} + + bool startCondition(Creature* creature) final; + bool executeCondition(Creature* creature, int32_t interval) final; + void endCondition(Creature* creature) final; + void addCondition(Creature* creature, const Condition* addCondition) final; + + ConditionLight* clone() const final { + return new ConditionLight(*this); + } + + bool setParam(ConditionParam_t param, int32_t value) final; + + //serialization + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + + protected: + LightInfo lightInfo; + uint32_t internalLightTicks = 0; + uint32_t lightChangeInterval = 0; +}; + +#endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp new file mode 100644 index 0000000..b61e306 --- /dev/null +++ b/src/configmanager.cpp @@ -0,0 +1,205 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "game.h" + +#if LUA_VERSION_NUM >= 502 +#undef lua_strlen +#define lua_strlen lua_rawlen +#endif + +extern Game g_game; + +bool ConfigManager::load() +{ + lua_State* L = luaL_newstate(); + if (!L) { + throw std::runtime_error("Failed to allocate memory"); + } + + luaL_openlibs(L); + + if (luaL_dofile(L, "config.lua")) { + std::cout << "[Error - ConfigManager::load] " << lua_tostring(L, -1) << std::endl; + lua_close(L); + return false; + } + + //parse config + if (!loaded) { //info that must be loaded one time (unless we reset the modules involved) + boolean[BIND_ONLY_GLOBAL_ADDRESS] = getGlobalBoolean(L, "bindOnlyGlobalAddress", false); + boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true); + + string[IP] = getGlobalString(L, "ip", "127.0.0.1"); + string[MAP_NAME] = getGlobalString(L, "mapName", "forgotten"); + string[MAP_AUTHOR] = getGlobalString(L, "mapAuthor", "Unknown"); + string[HOUSE_RENT_PERIOD] = getGlobalString(L, "houseRentPeriod", "never"); + string[MYSQL_HOST] = getGlobalString(L, "mysqlHost", "127.0.0.1"); + string[MYSQL_USER] = getGlobalString(L, "mysqlUser", "forgottenserver"); + string[MYSQL_PASS] = getGlobalString(L, "mysqlPass", ""); + string[MYSQL_DB] = getGlobalString(L, "mysqlDatabase", "forgottenserver"); + string[MYSQL_SOCK] = getGlobalString(L, "mysqlSock", ""); + + integer[SQL_PORT] = getGlobalNumber(L, "mysqlPort", 3306); + integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172); + integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171); + integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171); + } + + boolean[SHOW_MONSTER_LOOT] = getGlobalBoolean(L, "showMonsterLoot", true); + boolean[ALLOW_CHANGEOUTFIT] = getGlobalBoolean(L, "allowChangeOutfit", true); + boolean[ONE_PLAYER_ON_ACCOUNT] = getGlobalBoolean(L, "onePlayerOnlinePerAccount", true); + boolean[REMOVE_RUNE_CHARGES] = getGlobalBoolean(L, "removeChargesFromRunes", true); + boolean[EXPERIENCE_FROM_PLAYERS] = getGlobalBoolean(L, "experienceByKillingPlayers", false); + boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false); + boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true); + boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false); + boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true); + boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true); + boolean[TELEPORT_NEWBIES] = getGlobalBoolean(L, "teleportNewbies", true); + boolean[STACK_CUMULATIVES] = getGlobalBoolean(L, "autoStackCumulatives", false); + + string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); + string[SERVER_NAME] = getGlobalString(L, "serverName", ""); + string[OWNER_NAME] = getGlobalString(L, "ownerName", ""); + string[OWNER_EMAIL] = getGlobalString(L, "ownerEmail", ""); + string[URL] = getGlobalString(L, "url", ""); + string[LOCATION] = getGlobalString(L, "location", ""); + string[MOTD] = getGlobalString(L, "motd", ""); + string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp"); + + integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers"); + integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000); + integer[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2); + integer[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50); + integer[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExp", 5); + integer[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 3); + integer[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 2); + integer[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 3); + integer[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1); + integer[BAN_LENGTH] = getGlobalNumber(L, "banLength", 30 * 24 * 60 * 60); + integer[ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenActions", 200); + integer[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenExActions", 1000); + integer[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4); + integer[KICK_AFTER_MINUTES] = getGlobalNumber(L, "kickIdlePlayerAfterMinutes", 15); + integer[PROTECTION_LEVEL] = getGlobalNumber(L, "protectionLevel", 1); + integer[DEATH_LOSE_PERCENT] = getGlobalNumber(L, "deathLosePercent", -1); + integer[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 5000); + integer[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60); + integer[RED_SKULL_TIME] = getGlobalNumber(L, "redSkullTime", 30 * 24 * 60 * 60); + integer[KILLS_DAY_RED_SKULL] = getGlobalNumber(L, "killsDayRedSkull", 3); + integer[KILLS_WEEK_RED_SKULL] = getGlobalNumber(L, "killsWeekRedSkull", 5); + integer[KILLS_MONTH_RED_SKULL] = getGlobalNumber(L, "killsMonthRedSkull", 10); + integer[KILLS_DAY_BANISHMENT] = getGlobalNumber(L, "killsDayBanishment", 5); + integer[KILLS_WEEK_BANISHMENT] = getGlobalNumber(L, "killsWeekBanishment", 8); + integer[KILLS_MONTH_BANISHMENT] = getGlobalNumber(L, "killsMonthBanishment", 10); + integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000); + integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75); + integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); + integer[NEWBIE_TOWN] = getGlobalNumber(L, "newbieTownId", 1); + integer[NEWBIE_LEVEL_THRESHOLD] = getGlobalNumber(L, "newbieLevelThreshold", 5); + integer[MONEY_RATE] = getGlobalNumber(L, "moneyRate", 1); + + loaded = true; + lua_close(L); + return true; +} + +bool ConfigManager::reload() +{ + bool result = load(); + if (transformToSHA1(getString(ConfigManager::MOTD)) != g_game.getMotdHash()) { + g_game.incrementMotdNum(); + } + return result; +} + +const std::string& ConfigManager::getString(string_config_t what) const +{ + if (what >= LAST_STRING_CONFIG) { + std::cout << "[Warning - ConfigManager::getString] Accessing invalid index: " << what << std::endl; + return string[DUMMY_STR]; + } + return string[what]; +} + +int32_t ConfigManager::getNumber(integer_config_t what) const +{ + if (what >= LAST_INTEGER_CONFIG) { + std::cout << "[Warning - ConfigManager::getNumber] Accessing invalid index: " << what << std::endl; + return 0; + } + return integer[what]; +} + +bool ConfigManager::getBoolean(boolean_config_t what) const +{ + if (what >= LAST_BOOLEAN_CONFIG) { + std::cout << "[Warning - ConfigManager::getBoolean] Accessing invalid index: " << what << std::endl; + return false; + } + return boolean[what]; +} + +std::string ConfigManager::getGlobalString(lua_State* L, const char* identifier, const char* defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isstring(L, -1)) { + return defaultValue; + } + + size_t len = lua_strlen(L, -1); + std::string ret(lua_tostring(L, -1), len); + lua_pop(L, 1); + return ret; +} + +int32_t ConfigManager::getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isnumber(L, -1)) { + return defaultValue; + } + + int32_t val = lua_tonumber(L, -1); + lua_pop(L, 1); + return val; +} + +bool ConfigManager::getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isboolean(L, -1)) { + if (!lua_isstring(L, -1)) { + return defaultValue; + } + + size_t len = lua_strlen(L, -1); + std::string ret(lua_tostring(L, -1), len); + lua_pop(L, 1); + return booleanString(ret); + } + + int val = lua_toboolean(L, -1); + lua_pop(L, 1); + return val != 0; +} diff --git a/src/configmanager.h b/src/configmanager.h new file mode 100644 index 0000000..04c3a3b --- /dev/null +++ b/src/configmanager.h @@ -0,0 +1,129 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 +#define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 + +#include + +class ConfigManager +{ + public: + enum boolean_config_t { + SHOW_MONSTER_LOOT, + ALLOW_CHANGEOUTFIT, + ONE_PLAYER_ON_ACCOUNT, + REMOVE_RUNE_CHARGES, + EXPERIENCE_FROM_PLAYERS, + FREE_PREMIUM, + REPLACE_KICK_ON_LOGIN, + ALLOW_CLONES, + BIND_ONLY_GLOBAL_ADDRESS, + OPTIMIZE_DATABASE, + WARN_UNSAFE_SCRIPTS, + CONVERT_UNSAFE_SCRIPTS, + TELEPORT_NEWBIES, + STACK_CUMULATIVES, + + LAST_BOOLEAN_CONFIG /* this must be the last one */ + }; + + enum string_config_t { + DUMMY_STR, + MAP_NAME, + HOUSE_RENT_PERIOD, + SERVER_NAME, + OWNER_NAME, + OWNER_EMAIL, + URL, + LOCATION, + IP, + MOTD, + WORLD_TYPE, + MYSQL_HOST, + MYSQL_USER, + MYSQL_PASS, + MYSQL_DB, + MYSQL_SOCK, + DEFAULT_PRIORITY, + MAP_AUTHOR, + + LAST_STRING_CONFIG /* this must be the last one */ + }; + + enum integer_config_t { + SQL_PORT, + MAX_PLAYERS, + PZ_LOCKED, + DEFAULT_DESPAWNRANGE, + DEFAULT_DESPAWNRADIUS, + RATE_EXPERIENCE, + RATE_SKILL, + RATE_LOOT, + RATE_MAGIC, + RATE_SPAWN, + BAN_LENGTH, + MAX_MESSAGEBUFFER, + ACTIONS_DELAY_INTERVAL, + EX_ACTIONS_DELAY_INTERVAL, + KICK_AFTER_MINUTES, + PROTECTION_LEVEL, + DEATH_LOSE_PERCENT, + STATUSQUERY_TIMEOUT, + WHITE_SKULL_TIME, + RED_SKULL_TIME, + KILLS_DAY_RED_SKULL, + KILLS_WEEK_RED_SKULL, + KILLS_MONTH_RED_SKULL, + KILLS_DAY_BANISHMENT, + KILLS_WEEK_BANISHMENT, + KILLS_MONTH_BANISHMENT, + GAME_PORT, + LOGIN_PORT, + STATUS_PORT, + STAIRHOP_DELAY, + EXP_FROM_PLAYERS_LEVEL_RANGE, + MAX_PACKETS_PER_SECOND, + NEWBIE_TOWN, + NEWBIE_LEVEL_THRESHOLD, + MONEY_RATE, + + LAST_INTEGER_CONFIG /* this must be the last one */ + }; + + bool load(); + bool reload(); + + const std::string& getString(string_config_t what) const; + int32_t getNumber(integer_config_t what) const; + bool getBoolean(boolean_config_t what) const; + + private: + static std::string getGlobalString(lua_State* L, const char* identifier, const char* defaultValue); + static int32_t getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue = 0); + static bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue); + + std::string string[LAST_STRING_CONFIG] = {}; + int32_t integer[LAST_INTEGER_CONFIG] = {}; + bool boolean[LAST_BOOLEAN_CONFIG] = {}; + + bool loaded = false; +}; + +#endif diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..5f21835 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,298 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "connection.h" +#include "outputmessage.h" +#include "protocol.h" +#include "scheduler.h" +#include "server.h" + +extern ConfigManager g_config; + +Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort) +{ + std::lock_guard lockClass(connectionManagerLock); + + auto connection = std::make_shared(io_service, servicePort); + connections.insert(connection); + return connection; +} + +void ConnectionManager::releaseConnection(const Connection_ptr& connection) +{ + std::lock_guard lockClass(connectionManagerLock); + + connections.erase(connection); +} + +void ConnectionManager::closeAll() +{ + std::lock_guard lockClass(connectionManagerLock); + + for (const auto& connection : connections) { + try { + boost::system::error_code error; + connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + connection->socket.close(error); + } catch (boost::system::system_error&) { + } + } + connections.clear(); +} + +// Connection + +void Connection::close(bool force) +{ + //any thread + ConnectionManager::getInstance().releaseConnection(shared_from_this()); + + std::lock_guard lockClass(connectionLock); + if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + connectionState = CONNECTION_STATE_CLOSED; + + if (protocol) { + g_dispatcher.addTask( + createTask(std::bind(&Protocol::release, protocol))); + } + + if (messageQueue.empty() || force) { + closeSocket(); + } else { + //will be closed by the destructor or onWriteOperation + } +} + +void Connection::closeSocket() +{ + if (socket.is_open()) { + try { + readTimer.cancel(); + writeTimer.cancel(); + boost::system::error_code error; + socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error); + socket.close(error); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl; + } + } +} + +Connection::~Connection() +{ + closeSocket(); +} + +void Connection::accept(Protocol_ptr protocol) +{ + this->protocol = protocol; + g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol))); + + accept(); +} + +void Connection::accept() +{ + std::lock_guard lockClass(connectionLock); + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), std::placeholders::_1)); + + // Read size of the first packet + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::accept] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +void Connection::parseHeader(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + readTimer.cancel(); + + if (error) { + close(FORCE_CLOSE); + return; + } else if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + uint32_t timePassed = std::max(1, (time(nullptr) - timeConnected) + 1); + if ((++packetsSent / timePassed) > static_cast(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) { + std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl; + close(); + return; + } + + if (timePassed > 2) { + timeConnected = time(nullptr); + packetsSent = 0; + } + + uint16_t size = msg.getLengthHeader(); + if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) { + close(FORCE_CLOSE); + return; + } + + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + // Read packet content + msg.setLength(size + NetworkMessage::HEADER_LENGTH); + boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size), + std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +void Connection::parsePacket(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + readTimer.cancel(); + + if (error) { + close(FORCE_CLOSE); + return; + } else if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + if (!receivedFirst) { + // First message received + receivedFirst = true; + + if (!protocol) { + // Game protocol has already been created at this point + protocol = service_port->make_protocol(msg, shared_from_this()); + if (!protocol) { + close(FORCE_CLOSE); + return; + } + } else { + msg.skipBytes(1); // Skip protocol ID + } + + protocol->onRecvFirstMessage(msg); + } else { + protocol->onRecvMessage(msg); // Send the packet to the current protocol + } + + try { + readTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_READ_TIMEOUT)); + readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + // Wait to the next packet + boost::asio::async_read(socket, + boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH), + std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +void Connection::send(const OutputMessage_ptr& msg) +{ + std::lock_guard lockClass(connectionLock); + if (connectionState != CONNECTION_STATE_OPEN) { + return; + } + + bool noPendingWrite = messageQueue.empty(); + messageQueue.emplace_back(msg); + if (noPendingWrite) { + internalSend(msg); + } +} + +void Connection::internalSend(const OutputMessage_ptr& msg) +{ + protocol->onSendMessage(msg); + try { + writeTimer.expires_from_now(boost::posix_time::seconds(CONNECTION_WRITE_TIMEOUT)); + writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr(shared_from_this()), + std::placeholders::_1)); + + boost::asio::async_write(socket, + boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()), + std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1)); + } catch (boost::system::system_error& e) { + std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl; + close(FORCE_CLOSE); + } +} + +uint32_t Connection::getIP() +{ + std::lock_guard lockClass(connectionLock); + + // IP-address is expressed in network byte order + boost::system::error_code error; + const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error); + if (error) { + return 0; + } + + return htonl(endpoint.address().to_v4().to_ulong()); +} + +void Connection::onWriteOperation(const boost::system::error_code& error) +{ + std::lock_guard lockClass(connectionLock); + writeTimer.cancel(); + messageQueue.pop_front(); + + if (error) { + messageQueue.clear(); + close(FORCE_CLOSE); + return; + } + + if (!messageQueue.empty()) { + internalSend(messageQueue.front()); + } else if (connectionState == CONNECTION_STATE_CLOSED) { + closeSocket(); + } +} + +void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error) +{ + if (error == boost::asio::error::operation_aborted) { + //The timer has been manually cancelled + return; + } + + if (auto connection = connectionWeak.lock()) { + connection->close(FORCE_CLOSE); + } +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..34c6ac9 --- /dev/null +++ b/src/connection.h @@ -0,0 +1,137 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 +#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348 + +#include + +#include "networkmessage.h" + +static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; +static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; + +class Protocol; +typedef std::shared_ptr Protocol_ptr; +class OutputMessage; +typedef std::shared_ptr OutputMessage_ptr; +class Connection; +typedef std::shared_ptr Connection_ptr; +typedef std::weak_ptr ConnectionWeak_ptr; +class ServiceBase; +typedef std::shared_ptr Service_ptr; +class ServicePort; +typedef std::shared_ptr ServicePort_ptr; +typedef std::shared_ptr ConstServicePort_ptr; + +class ConnectionManager +{ + public: + static ConnectionManager& getInstance() { + static ConnectionManager instance; + return instance; + } + + Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort); + void releaseConnection(const Connection_ptr& connection); + void closeAll(); + + protected: + ConnectionManager() = default; + + std::unordered_set connections; + std::mutex connectionManagerLock; +}; + +class Connection : public std::enable_shared_from_this +{ + public: + // non-copyable + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; + + enum ConnectionState_t { + CONNECTION_STATE_OPEN, + CONNECTION_STATE_CLOSED, + }; + + enum { FORCE_CLOSE = true }; + + Connection(boost::asio::io_service& io_service, + ConstServicePort_ptr service_port) : + readTimer(io_service), + writeTimer(io_service), + service_port(std::move(service_port)), + socket(io_service) { + connectionState = CONNECTION_STATE_OPEN; + receivedFirst = false; + packetsSent = 0; + timeConnected = time(nullptr); + } + ~Connection(); + + friend class ConnectionManager; + + void close(bool force = false); + // Used by protocols that require server to send first + void accept(Protocol_ptr protocol); + void accept(); + + void send(const OutputMessage_ptr& msg); + + uint32_t getIP(); + + private: + void parseHeader(const boost::system::error_code& error); + void parsePacket(const boost::system::error_code& error); + + void onWriteOperation(const boost::system::error_code& error); + + static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error); + + void closeSocket(); + void internalSend(const OutputMessage_ptr& msg); + + boost::asio::ip::tcp::socket& getSocket() { + return socket; + } + friend class ServicePort; + + NetworkMessage msg; + + boost::asio::deadline_timer readTimer; + boost::asio::deadline_timer writeTimer; + + std::recursive_mutex connectionLock; + + std::list messageQueue; + + ConstServicePort_ptr service_port; + Protocol_ptr protocol; + + boost::asio::ip::tcp::socket socket; + + time_t timeConnected; + uint32_t packetsSent; + + bool connectionState; + bool receivedFirst; +}; + +#endif diff --git a/src/const.h b/src/const.h new file mode 100644 index 0000000..afefe77 --- /dev/null +++ b/src/const.h @@ -0,0 +1,316 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B +#define FS_CONST_H_0A49B5996F074465BF44B90F4F780E8B + +static constexpr int32_t NETWORKMESSAGE_MAXSIZE = 24590; + +enum MagicEffectClasses : uint8_t { + CONST_ME_NONE, + + CONST_ME_DRAWBLOOD = 1, + CONST_ME_LOSEENERGY = 2, + CONST_ME_POFF = 3, + CONST_ME_BLOCKHIT = 4, + CONST_ME_EXPLOSIONAREA = 5, + CONST_ME_EXPLOSIONHIT = 6, + CONST_ME_FIREAREA = 7, + CONST_ME_YELLOW_RINGS = 8, + CONST_ME_GREEN_RINGS = 9, + CONST_ME_HITAREA = 10, + CONST_ME_TELEPORT = 11, + CONST_ME_ENERGYHIT = 12, + CONST_ME_MAGIC_BLUE = 13, + CONST_ME_MAGIC_RED = 14, + CONST_ME_MAGIC_GREEN = 15, + CONST_ME_HITBYFIRE = 16, + CONST_ME_HITBYPOISON = 17, + CONST_ME_MORTAREA = 18, + CONST_ME_SOUND_GREEN = 19, + CONST_ME_SOUND_RED = 20, + CONST_ME_POISONAREA = 21, + CONST_ME_SOUND_YELLOW = 22, + CONST_ME_SOUND_PURPLE = 23, + CONST_ME_SOUND_BLUE = 24, + CONST_ME_SOUND_WHITE = 25, +}; + +enum ShootType_t : uint8_t { + CONST_ANI_NONE, + + CONST_ANI_SPEAR = 1, + CONST_ANI_BOLT = 2, + CONST_ANI_ARROW = 3, + CONST_ANI_FIRE = 4, + CONST_ANI_ENERGY = 5, + CONST_ANI_POISONARROW = 6, + CONST_ANI_BURSTARROW = 7, + CONST_ANI_THROWINGSTAR = 8, + CONST_ANI_THROWINGKNIFE = 9, + CONST_ANI_SMALLSTONE = 10, + CONST_ANI_DEATH = 11, + CONST_ANI_LARGEROCK = 12, + CONST_ANI_SNOWBALL = 13, + CONST_ANI_POWERBOLT = 14, + CONST_ANI_POISON = 15, +}; + +enum SpeakClasses : uint8_t { + TALKTYPE_SAY = 1, + TALKTYPE_WHISPER = 2, + TALKTYPE_YELL = 3, + TALKTYPE_PRIVATE = 4, + TALKTYPE_CHANNEL_Y = 5, // Yellow + TALKTYPE_RVR_CHANNEL = 6, + TALKTYPE_RVR_ANSWER = 7, + TALKTYPE_RVR_CONTINUE = 8, + TALKTYPE_BROADCAST = 9, + TALKTYPE_CHANNEL_R1 = 10, // Red - #c text + TALKTYPE_PRIVATE_RED = 11, // @name@text + TALKTYPE_CHANNEL_O = 12, // orange + TALKTYPE_CHANNEL_R2 = 13, // red anonymous - #d text + TALKTYPE_MONSTER_YELL = 0x10, + TALKTYPE_MONSTER_SAY = 0x11, +}; + +enum MessageClasses : uint8_t { + MESSAGE_STATUS_CONSOLE_YELLOW = 0x01, //Yellow message in the console + MESSAGE_STATUS_CONSOLE_LBLUE = 0x04, //Light blue message in the console + MESSAGE_STATUS_CONSOLE_ORANGE = 0x11, //Orange message in the console + MESSAGE_STATUS_WARNING = 0x12, //Red message in game window and in the console + MESSAGE_EVENT_ADVANCE = 0x13, //White message in game window and in the console + MESSAGE_EVENT_DEFAULT = 0x14, //White message at the bottom of the game window and in the console + MESSAGE_STATUS_DEFAULT = 0x15, //White message at the bottom of the game window and in the console + MESSAGE_INFO_DESCR = 0x16, //Green message in game window and in the console + MESSAGE_STATUS_SMALL = 0x17, //White message at the bottom of the game window" + MESSAGE_STATUS_CONSOLE_BLUE = 0x18, //Blue message in the console + MESSAGE_STATUS_CONSOLE_RED = 0x19, //Red message in the console + + MESSAGE_CLASS_FIRST = MESSAGE_STATUS_CONSOLE_YELLOW, + MESSAGE_CLASS_LAST = MESSAGE_STATUS_CONSOLE_RED, +}; + +enum FluidTypes_t : uint8_t +{ + FLUID_NONE = 0, + FLUID_WATER, + FLUID_WINE, + FLUID_BEER, + FLUID_MUD, + FLUID_BLOOD, + FLUID_SLIME, + FLUID_OIL, + FLUID_URINE, + FLUID_MILK, + FLUID_MANAFLUID, + FLUID_LIFEFLUID, + FLUID_LEMONADE, +}; + +enum FluidColor_t : uint8_t +{ + FLUID_COLOR_NONE = 0, + FLUID_COLOR_BLUE = 1, + FLUID_COLOR_PURPLE = 2, + FLUID_COLOR_BROWN = 3, + FLUID_COLOR_RED = 4, + FLUID_COLOR_GREEN = 5, + FLUID_COLOR_YELLOW = 6, + FLUID_COLOR_WHITE = 7, +}; + +enum SquareColor_t : uint8_t { + SQ_COLOR_BLACK = 0, +}; + +enum TextColor_t : uint8_t { + TEXTCOLOR_BLUE = 5, + TEXTCOLOR_LIGHTGREEN = 30, + TEXTCOLOR_LIGHTBLUE = 35, + TEXTCOLOR_MAYABLUE = 95, + TEXTCOLOR_DARKRED = 108, + TEXTCOLOR_LIGHTGREY = 129, + TEXTCOLOR_SKYBLUE = 143, + TEXTCOLOR_PURPLE = 155, + TEXTCOLOR_RED = 180, + TEXTCOLOR_ORANGE = 198, + TEXTCOLOR_YELLOW = 210, + TEXTCOLOR_WHITE_EXP = 215, + TEXTCOLOR_NONE = 255, +}; + +enum Icons_t { + ICON_POISON = 1 << 0, + ICON_BURN = 1 << 1, + ICON_ENERGY = 1 << 2, + ICON_DRUNK = 1 << 3, + ICON_MANASHIELD = 1 << 4, + ICON_PARALYZE = 1 << 5, + ICON_HASTE = 1 << 6, + ICON_SWORDS = 1 << 7 +}; + +enum WeaponType_t : uint8_t { + WEAPON_NONE, + WEAPON_SWORD, + WEAPON_CLUB, + WEAPON_AXE, + WEAPON_SHIELD, + WEAPON_DISTANCE, + WEAPON_WAND, + WEAPON_AMMO, +}; + +enum Ammo_t : uint8_t { + AMMO_NONE, + AMMO_BOLT, + AMMO_ARROW, + AMMO_SPEAR, + AMMO_THROWINGSTAR, + AMMO_THROWINGKNIFE, + AMMO_STONE, + AMMO_SNOWBALL, +}; + +enum WeaponAction_t : uint8_t { + WEAPONACTION_NONE, + WEAPONACTION_REMOVECOUNT, + WEAPONACTION_REMOVECHARGE, + WEAPONACTION_MOVE, +}; + +enum WieldInfo_t { + WIELDINFO_LEVEL = 1 << 0, + WIELDINFO_MAGLV = 1 << 1, + WIELDINFO_VOCREQ = 1 << 2, + WIELDINFO_PREMIUM = 1 << 3, +}; + +enum Skulls_t : uint8_t { + SKULL_NONE = 0, + SKULL_YELLOW = 1, + SKULL_GREEN = 2, + SKULL_WHITE = 3, + SKULL_RED = 4, +}; + +enum PartyShields_t : uint8_t { + SHIELD_NONE = 0, + SHIELD_WHITEYELLOW = 1, + SHIELD_WHITEBLUE = 2, + SHIELD_BLUE = 3, + SHIELD_YELLOW = 4 +}; + +enum item_t : uint16_t { + ITEM_FIREFIELD_PVP_FULL = 2118, + ITEM_FIREFIELD_PVP_MEDIUM = 2119, + ITEM_FIREFIELD_PVP_SMALL = 2120, + ITEM_FIREFIELD_PERSISTENT_FULL = 2123, + ITEM_FIREFIELD_PERSISTENT_MEDIUM = 2124, + ITEM_FIREFIELD_PERSISTENT_SMALL = 2125, + ITEM_FIREFIELD_NOPVP = 2131, + + ITEM_POISONFIELD_PVP = 2121, + ITEM_POISONFIELD_PERSISTENT = 2127, + ITEM_POISONFIELD_NOPVP = 2134, + + ITEM_ENERGYFIELD_PVP = 2122, + ITEM_ENERGYFIELD_PERSISTENT = 2126, + ITEM_ENERGYFIELD_NOPVP = 2135, + + ITEM_MAGICWALL = 2128, + ITEM_MAGICWALL_PERSISTENT = 2128, + + ITEM_WILDGROWTH = 2130, + ITEM_WILDGROWTH_PERSISTENT = 2130, + + ITEM_GOLD_COIN = 3031, + ITEM_PLATINUM_COIN = 3035, + ITEM_CRYSTAL_COIN = 3043, + + ITEM_DEPOT = 3502, + ITEM_LOCKER1 = 3497, + + ITEM_MALE_CORPSE = 4240, + ITEM_FEMALE_CORPSE = 4247, + + ITEM_FULLSPLASH = 2886, + ITEM_SMALLSPLASH = 2889, + + ITEM_PARCEL = 3503, + ITEM_PARCEL_STAMPED = 3504, + ITEM_LETTER = 3505, + ITEM_LETTER_STAMPED = 3506, + ITEM_LABEL = 3507, + + ITEM_AMULETOFLOSS = 3057, + + ITEM_DOCUMENT_RO = 2819, //read-only +}; + +enum PlayerFlags : uint64_t { + PlayerFlag_CannotUseCombat = 1 << 0, + PlayerFlag_CannotAttackPlayer = 1 << 1, + PlayerFlag_CannotAttackMonster = 1 << 2, + PlayerFlag_CannotBeAttacked = 1 << 3, + PlayerFlag_CanConvinceAll = 1 << 4, + PlayerFlag_CanSummonAll = 1 << 5, + PlayerFlag_CanIllusionAll = 1 << 6, + PlayerFlag_CanSenseInvisibility = 1 << 7, + PlayerFlag_IgnoredByMonsters = 1 << 8, + PlayerFlag_NotGainInFight = 1 << 9, + PlayerFlag_HasInfiniteMana = 1 << 10, + PlayerFlag_HasInfiniteSoul = 1 << 11, + PlayerFlag_HasNoExhaustion = 1 << 12, + PlayerFlag_CannotUseSpells = 1 << 13, + PlayerFlag_CannotPickupItem = 1 << 14, + PlayerFlag_CanAlwaysLogin = 1 << 15, + PlayerFlag_CanBroadcast = 1 << 16, + PlayerFlag_CanEditHouses = 1 << 17, + PlayerFlag_CannotBeBanned = 1 << 18, + PlayerFlag_CannotBePushed = 1 << 19, + PlayerFlag_HasInfiniteCapacity = 1 << 20, + PlayerFlag_CanPushAllCreatures = 1 << 21, + PlayerFlag_CanTalkRedPrivate = 1 << 22, + PlayerFlag_CanTalkRedChannel = 1 << 23, + PlayerFlag_TalkOrangeHelpChannel = 1 << 24, + PlayerFlag_NotGainExperience = 1 << 25, + PlayerFlag_NotGainMana = 1 << 26, + PlayerFlag_NotGainHealth = 1 << 27, + PlayerFlag_NotGainSkill = 1 << 28, + PlayerFlag_SetMaxSpeed = 1 << 29, + PlayerFlag_SpecialVIP = 1 << 30, + PlayerFlag_NotGenerateLoot = static_cast(1) << 31, + PlayerFlag_CanTalkRedChannelAnonymous = static_cast(1) << 32, + PlayerFlag_IgnoreProtectionZone = static_cast(1) << 33, + PlayerFlag_IgnoreSpellCheck = static_cast(1) << 34, + PlayerFlag_IgnoreWeaponCheck = static_cast(1) << 35, + PlayerFlag_CannotBeMuted = static_cast(1) << 36, + PlayerFlag_IsAlwaysPremium = static_cast(1) << 37, + PlayerFlag_SpecialMoveUse = static_cast(1) << 38, +}; + +static constexpr int32_t CHANNEL_GUILD = 0x00; +static constexpr int32_t CHANNEL_PARTY = 0x01; +static constexpr int32_t CHANNEL_RULE_REP = 0x02; +static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; + +#endif diff --git a/src/container.cpp b/src/container.cpp new file mode 100644 index 0000000..e404695 --- /dev/null +++ b/src/container.cpp @@ -0,0 +1,690 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "container.h" +#include "iomap.h" +#include "game.h" + +extern Game g_game; + +Container::Container(uint16_t type) : + Container(type, items[type].maxItems) {} + +Container::Container(uint16_t type, uint16_t size) : + Item(type), + maxSize(size) +{} + +Container::~Container() +{ + for (Item* item : itemlist) { + item->setParent(nullptr); + item->decrementReferenceCounter(); + } +} + +Item* Container::clone() const +{ + Container* clone = static_cast(Item::clone()); + for (Item* item : itemlist) { + clone->addItem(item->clone()); + } + clone->totalWeight = totalWeight; + return clone; +} + +Container* Container::getParentContainer() +{ + Thing* thing = getParent(); + if (!thing) { + return nullptr; + } + return thing->getContainer(); +} + +bool Container::hasParent() const +{ + return dynamic_cast(getParent()) != nullptr; +} + +void Container::addItem(Item* item) +{ + itemlist.push_back(item); + item->setParent(this); +} + +Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_CONTAINER_ITEMS) { + if (!propStream.read(serializationCount)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_END; + } + return Item::readAttr(attr, propStream); +} + +bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) +{ + bool ret = Item::unserializeItemNode(f, node, propStream); + if (!ret) { + return false; + } + + uint32_t type; + NODE nodeItem = f.getChildNode(node, type); + while (nodeItem) { + //load container items + if (type != OTBM_ITEM) { + // unknown type + return false; + } + + PropStream itemPropStream; + if (!f.getProps(nodeItem, itemPropStream)) { + return false; + } + + Item* item = Item::CreateItem(itemPropStream); + if (!item) { + return false; + } + + if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) { + return false; + } + + addItem(item); + updateItemWeight(item->getWeight()); + + nodeItem = f.getNextNode(nodeItem, type); + } + return true; +} + +void Container::updateItemWeight(int32_t diff) +{ + totalWeight += diff; + if (Container* parentContainer = getParentContainer()) { + parentContainer->updateItemWeight(diff); + } +} + +uint32_t Container::getWeight() const +{ + return Item::getWeight() + totalWeight; +} + +std::string Container::getContentDescription() const +{ + std::ostringstream os; + return getContentDescription(os).str(); +} + +std::ostringstream& Container::getContentDescription(std::ostringstream& os) const +{ + bool firstitem = true; + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + Item* item = *it; + + Container* container = item->getContainer(); + if (container && !container->empty()) { + continue; + } + + if (firstitem) { + firstitem = false; + } else { + os << ", "; + } + + os << item->getNameDescription(); + } + + if (firstitem) { + os << "nothing"; + } + return os; +} + +Item* Container::getItemByIndex(size_t index) const +{ + if (index >= size()) { + return nullptr; + } + return itemlist[index]; +} + +uint32_t Container::getItemHoldingCount() const +{ + uint32_t counter = 0; + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + ++counter; + } + return counter; +} + +bool Container::isHoldingItem(const Item* item) const +{ + for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) { + if (*it == item) { + return true; + } + } + return false; +} + +void Container::onAddContainerItem(Item* item) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendAddContainerItem(this, item); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onAddContainerItem(item); + } +} + +void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem); + } +} + +void Container::onRemoveContainerItem(uint32_t index, Item* item) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + + //send change to client + for (Creature* spectator : list) { + spectator->getPlayer()->sendRemoveContainerItem(this, index); + } + + //event methods + for (Creature* spectator : list) { + spectator->getPlayer()->onRemoveContainerItem(this, item); + } +} + +ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor/* = nullptr*/) const +{ + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + if (childIsOwner) { + //a child container is querying, since we are the top container (not carried by a player) + //just return with no error. + return RETURNVALUE_NOERROR; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isPickupable()) { + return RETURNVALUE_CANNOTPICKUP; + } + + if (item == this) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + const Cylinder* cylinder = getParent(); + if (!hasBitSet(FLAG_NOLIMIT, flags)) { + while (cylinder) { + if (cylinder == &thing) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + cylinder = cylinder->getParent(); + } + + if (index == INDEX_WHEREEVER && size() >= capacity()) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } else { + while (cylinder) { + if (cylinder == &thing) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + cylinder = cylinder->getParent(); + } + } + + const Cylinder* topParent = getTopParent(); + if (topParent != this) { + return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor); + } else { + return RETURNVALUE_NOERROR; + } +} + +ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + maxQueryCount = 0; + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; + } + + int32_t freeSlots = std::max(capacity() - size(), 0); + + if (item->isStackable()) { + uint32_t n = 0; + + if (index == INDEX_WHEREEVER) { + //Iterate through every item and check how much free stackable slots there is. + uint32_t slotIndex = 0; + for (Item* containerItem : itemlist) { + if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) { + uint32_t remainder = (100 - containerItem->getItemCount()); + if (queryAdd(slotIndex++, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n += remainder; + } + } + } + } else { + const Item* destItem = getItemByIndex(index); + if (item->equals(destItem) && destItem->getItemCount() < 100) { + uint32_t remainder = 100 - destItem->getItemCount(); + if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n = remainder; + } + } + } + + maxQueryCount = freeSlots * 100 + n; + if (maxQueryCount < count) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } else { + maxQueryCount = freeSlots; + if (maxQueryCount == 0) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + return RETURNVALUE_NOERROR; +} + +Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem, + uint32_t& flags) +{ + if (index == 254 /*move up*/) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + + Container* parentContainer = dynamic_cast(getParent()); + if (parentContainer) { + return parentContainer; + } + return this; + } + + if (index == 255 /*add wherever*/) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + } else if (index >= static_cast(capacity())) { + /* + if you have a container, maximize it to show all 20 slots + then you open a bag that is inside the container you will have a bag with 8 slots + and a "grey" area where the other 12 slots where from the container + if you drop the item on that grey area + the client calculates the slot position as if the bag has 20 slots + */ + index = INDEX_WHEREEVER; + *destItem = nullptr; + } + + const Item* item = thing.getItem(); + if (!item) { + return this; + } + + if (g_config.getBoolean(ConfigManager::STACK_CUMULATIVES)) { + bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags); + if (autoStack && item->isStackable() && item->getParent() != this) { + //try find a suitable item to stack with + uint32_t n = 0; + for (Item* listItem : itemlist) { + if (listItem != item && listItem->equals(item) && listItem->getItemCount() < 100) { + *destItem = listItem; + index = n; + return this; + } + ++n; + } + } + } + + if (index != INDEX_WHEREEVER) { + Item* itemFromIndex = getItemByIndex(index); + if (itemFromIndex) { + *destItem = itemFromIndex; + } + + Cylinder* subCylinder = dynamic_cast(*destItem); + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + return subCylinder; + } + } + return this; +} + +void Container::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Container::addThing(int32_t index, Thing* thing) +{ + if (index >= static_cast(capacity())) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + itemlist.push_front(item); + updateItemWeight(item->getWeight()); + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::addItemBack(Item* item) +{ + addItem(item); + updateItemWeight(item->getWeight()); + + //send change to client + if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) { + onAddContainerItem(item); + } +} + +void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + const int32_t oldWeight = item->getWeight(); + item->setID(itemId); + item->setSubType(count); + updateItemWeight(-oldWeight + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, item, item); + } +} + +void Container::replaceThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* replacedItem = getItemByIndex(index); + if (!replacedItem) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + itemlist[index] = item; + item->setParent(this); + updateItemWeight(-static_cast(replacedItem->getWeight()) + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, replacedItem, item); + } + + replacedItem->setParent(nullptr); +} + +void Container::removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (item->isStackable() && count != item->getItemCount()) { + uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); + const int32_t oldWeight = item->getWeight(); + item->setItemCount(newCount); + updateItemWeight(-oldWeight + item->getWeight()); + + //send change to client + if (getParent()) { + onUpdateContainerItem(index, item, item); + } + } else { + updateItemWeight(-static_cast(item->getWeight())); + + //send change to client + if (getParent()) { + onRemoveContainerItem(index, item); + } + + item->setParent(nullptr); + itemlist.erase(itemlist.begin() + index); + } +} + +int32_t Container::getThingIndex(const Thing* thing) const +{ + int32_t index = 0; + for (Item* item : itemlist) { + if (item == thing) { + return index; + } + ++index; + } + return -1; +} + +size_t Container::getFirstIndex() const +{ + return 0; +} + +size_t Container::getLastIndex() const +{ + return size(); +} + +uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const +{ + uint32_t count = 0; + for (Item* item : itemlist) { + if (item->getID() == itemId) { + count += countByType(item, subType); + } + } + return count; +} + +std::map& Container::getAllItemTypeCount(std::map& countMap) const +{ + for (Item* item : itemlist) { + countMap[item->getID()] += item->getItemCount(); + } + return countMap; +} + +Thing* Container::getThing(size_t index) const +{ + return getItemByIndex(index); +} + +void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + Cylinder* topParent = getTopParent(); + if (topParent->getCreature()) { + topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT); + } else if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + } else { + topParent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + Cylinder* topParent = getTopParent(); + if (topParent->getCreature()) { + topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT); + } else if (topParent == this) { + //let the tile class notify surrounding players + if (topParent->getParent()) { + topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR); + } + } else { + topParent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} + +void Container::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Container::internalAddThing(uint32_t, Thing* thing) +{ + Item* item = thing->getItem(); + if (item == nullptr) { + return; + } + + item->setParent(this); + itemlist.push_front(item); + updateItemWeight(item->getWeight()); +} + +void Container::startDecaying() +{ + for (Item* item : itemlist) { + item->startDecaying(); + } +} + +ContainerIterator Container::iterator() const +{ + ContainerIterator cit; + if (!itemlist.empty()) { + cit.over.push_back(this); + cit.cur = itemlist.begin(); + } + return cit; +} + +Item* ContainerIterator::operator*() +{ + return *cur; +} + +void ContainerIterator::advance() +{ + if (Item* i = *cur) { + if (Container* c = i->getContainer()) { + if (!c->empty()) { + over.push_back(c); + } + } + } + + ++cur; + + if (cur == over.front()->itemlist.end()) { + over.pop_front(); + if (!over.empty()) { + cur = over.front()->itemlist.begin(); + } + } +} diff --git a/src/container.h b/src/container.h new file mode 100644 index 0000000..7af5675 --- /dev/null +++ b/src/container.h @@ -0,0 +1,162 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC +#define FS_CONTAINER_H_5590165FD8A2451B98D71F13CD3ED8DC + +#include + +#include "cylinder.h" +#include "item.h" + +class Container; +class DepotLocker; + +class ContainerIterator +{ + public: + bool hasNext() const { + return !over.empty(); + } + + void advance(); + Item* operator*(); + + protected: + std::list over; + ItemDeque::const_iterator cur; + + friend class Container; +}; + +class Container : public Item, public Cylinder +{ + public: + explicit Container(uint16_t type); + Container(uint16_t type, uint16_t size); + ~Container(); + + // non-copyable + Container(const Container&) = delete; + Container& operator=(const Container&) = delete; + + Item* clone() const final; + + Container* getContainer() final { + return this; + } + const Container* getContainer() const final { + return this; + } + + virtual DepotLocker* getDepotLocker() { + return nullptr; + } + virtual const DepotLocker* getDepotLocker() const { + return nullptr; + } + + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) override; + std::string getContentDescription() const; + + size_t size() const { + return itemlist.size(); + } + bool empty() const { + return itemlist.empty(); + } + uint32_t capacity() const { + return maxSize; + } + + ContainerIterator iterator() const; + + const ItemDeque& getItemList() const { + return itemlist; + } + + ItemDeque::const_reverse_iterator getReversedItems() const { + return itemlist.rbegin(); + } + ItemDeque::const_reverse_iterator getReversedEnd() const { + return itemlist.rend(); + } + + bool hasParent() const; + void addItem(Item* item); + Item* getItemByIndex(size_t index) const; + bool isHoldingItem(const Item* item) const; + + uint32_t getItemHoldingCount() const; + uint32_t getWeight() const final; + + //cylinder implementations + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + void addItemBack(Item* item); + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + std::map& getAllItemTypeCount(std::map& countMap) const final; + Thing* getThing(size_t index) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + void startDecaying() final; + + private: + void onAddContainerItem(Item* item); + void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); + void onRemoveContainerItem(uint32_t index, Item* item); + + Container* getParentContainer(); + void updateItemWeight(int32_t diff); + + protected: + std::ostringstream& getContentDescription(std::ostringstream& os) const; + + uint32_t maxSize; + uint32_t totalWeight = 0; + ItemDeque itemlist; + uint32_t serializationCount = 0; + + friend class ContainerIterator; + friend class IOMapSerialize; +}; + +#endif diff --git a/src/creature.cpp b/src/creature.cpp new file mode 100644 index 0000000..781c87d --- /dev/null +++ b/src/creature.cpp @@ -0,0 +1,1567 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "creature.h" +#include "game.h" +#include "monster.h" +#include "configmanager.h" +#include "scheduler.h" + +extern Game g_game; +extern ConfigManager g_config; +extern CreatureEvents* g_creatureEvents; + +Creature::Creature() +{ + onIdleStatus(); +} + +Creature::~Creature() +{ + for (Creature* summon : summons) { + summon->setAttackedCreature(nullptr); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + + for (Condition* condition : conditions) { + condition->endCondition(this); + delete condition; + } +} + +bool Creature::canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY) +{ + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (pos.z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (Position::getDistanceZ(myPos, pos) > 2) { + return false; + } + } + + const int_fast32_t offsetz = myPos.getZ() - pos.getZ(); + return (pos.getX() >= myPos.getX() - viewRangeX + offsetz) && (pos.getX() <= myPos.getX() + viewRangeX + offsetz) + && (pos.getY() >= myPos.getY() - viewRangeY + offsetz) && (pos.getY() <= myPos.getY() + viewRangeY + offsetz); +} + +bool Creature::canSee(const Position& pos) const +{ + return canSee(getPosition(), pos, Map::maxViewportX, Map::maxViewportY); +} + +bool Creature::canSeeCreature(const Creature* creature) const +{ + if (!canSeeInvisibility() && creature->isInvisible()) { + return false; + } + return true; +} + +void Creature::setSkull(Skulls_t newSkull) +{ + skull = newSkull; + g_game.updateCreatureSkull(this); +} + +int64_t Creature::getTimeSinceLastMove() const +{ + if (lastStep) { + return OTSYS_TIME() - lastStep; + } + return std::numeric_limits::max(); +} + +int32_t Creature::getWalkDelay(Direction dir) const +{ + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration(dir); + return stepDuration - (ct - lastStep); +} + +int32_t Creature::getWalkDelay() const +{ + //Used for auto-walking + if (lastStep == 0) { + return 0; + } + + int64_t ct = OTSYS_TIME(); + int64_t stepDuration = getStepDuration() * lastStepCost; + return stepDuration - (ct - lastStep); +} + +void Creature::onThink(uint32_t interval) +{ + if (!isMapLoaded && useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (followCreature && master != followCreature && !canSeeCreature(followCreature)) { + onCreatureDisappear(followCreature, false); + } + + if (attackedCreature && master != attackedCreature && !canSeeCreature(attackedCreature)) { + onCreatureDisappear(attackedCreature, false); + } + + blockTicks += interval; + if (blockTicks >= 1000) { + blockCount = std::min(blockCount + 1, 2); + blockTicks = 0; + } + + if (followCreature) { + walkUpdateTicks += interval; + if (forceUpdateFollowPath || walkUpdateTicks >= 2000) { + walkUpdateTicks = 0; + forceUpdateFollowPath = false; + isUpdatingPath = true; + } + } + + if (isUpdatingPath) { + isUpdatingPath = false; + goToFollowCreature(); + } + + //scripting event - onThink + const CreatureEventList& thinkEvents = getCreatureEvents(CREATURE_EVENT_THINK); + for (CreatureEvent* thinkEvent : thinkEvents) { + thinkEvent->executeOnThink(this, interval); + } +} + +void Creature::onAttacking(uint32_t interval) +{ + if (!attackedCreature) { + return; + } + + onAttacked(); + attackedCreature->onAttacked(); + + if (g_game.isSightClear(getPosition(), attackedCreature->getPosition(), true)) { + doAttacking(interval); + } +} + +void Creature::onIdleStatus() +{ + if (getHealth() > 0) { + damageMap.clear(); + lastHitCreatureId = 0; + } +} + +void Creature::onWalk() +{ + if (getWalkDelay() <= 0) { + Direction dir; + uint32_t flags = FLAG_IGNOREFIELDDAMAGE; + if (getNextStep(dir, flags)) { + ReturnValue ret = g_game.internalMoveCreature(this, dir, flags); + if (ret != RETURNVALUE_NOERROR) { + if (Player* player = getPlayer()) { + player->sendCancelMessage(ret); + player->sendCancelWalk(); + } + + forceUpdateFollowPath = true; + } + } else { + if (listWalkDir.empty()) { + onWalkComplete(); + } + + stopEventWalk(); + } + } + + if (cancelNextWalk) { + listWalkDir.clear(); + onWalkAborted(); + cancelNextWalk = false; + } + + if (eventWalk != 0) { + eventWalk = 0; + addEventWalk(); + } +} + +void Creature::onWalk(Direction& dir) +{ + if (hasCondition(CONDITION_DRUNK)) { + uint32_t r = uniform_random(0, 20); + if (r <= DIRECTION_DIAGONAL_MASK) { + if (r < DIRECTION_DIAGONAL_MASK) { + dir = static_cast(r); + } + g_game.internalCreatureSay(this, TALKTYPE_SAY, "Hicks!", false); + } + } +} + +bool Creature::getNextStep(Direction& dir, uint32_t&) +{ + if (listWalkDir.empty()) { + return false; + } + + dir = listWalkDir.front(); + listWalkDir.pop_front(); + onWalk(dir); + return true; +} + +void Creature::startAutoWalk(const std::forward_list& listDir) +{ + listWalkDir = listDir; + + size_t size = 0; + for (auto it = listDir.begin(); it != listDir.end() && size <= 1; ++it) { + size++; + } + addEventWalk(size == 1); +} + +void Creature::addEventWalk(bool firstStep) +{ + cancelNextWalk = false; + + if (getStepSpeed() <= 0) { + return; + } + + if (eventWalk != 0) { + return; + } + + int64_t ticks = getEventStepTicks(firstStep); + if (ticks <= 0) { + return; + } + + // Take first step right away, but still queue the next + if (ticks == 1) { + g_game.checkCreatureWalk(getID()); + } + + eventWalk = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Game::checkCreatureWalk, &g_game, getID()))); +} + +void Creature::stopEventWalk() +{ + if (eventWalk != 0) { + g_scheduler.stopEvent(eventWalk); + eventWalk = 0; + } +} + +void Creature::updateMapCache() +{ + Tile* tile; + const Position& myPos = getPosition(); + Position pos(0, 0, myPos.z); + + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + pos.x = myPos.getX() + x; + pos.y = myPos.getY() + y; + tile = g_game.map.getTile(pos); + updateTileCache(tile, pos); + } + } +} + +void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) +{ + if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { + localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR; + } +} + +void Creature::updateTileCache(const Tile* tile, const Position& pos) +{ + const Position& myPos = getPosition(); + if (pos.z == myPos.z) { + int32_t dx = Position::getOffsetX(pos, myPos); + int32_t dy = Position::getOffsetY(pos, myPos); + updateTileCache(tile, dx, dy); + } +} + +int32_t Creature::getWalkCache(const Position& pos) const +{ + if (!useCacheMap()) { + return 2; + } + + const Position& myPos = getPosition(); + if (myPos.z != pos.z) { + return 0; + } + + if (pos == myPos) { + return 1; + } + + int32_t dx = Position::getOffsetX(pos, myPos); + if (std::abs(dx) <= maxWalkCacheWidth) { + int32_t dy = Position::getOffsetY(pos, myPos); + if (std::abs(dy) <= maxWalkCacheHeight) { + if (localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx]) { + return 1; + } else { + return 0; + } + } + } + + //out of range + return 2; +} + +void Creature::onAddTileItem(const Tile* tile, const Position& pos) +{ + if (isMapLoaded && pos.z == getPosition().z) { + updateTileCache(tile, pos); + } +} + +void Creature::onUpdateTileItem(const Tile* tile, const Position& pos, const Item*, + const ItemType& oldType, const Item*, const ItemType& newType) +{ + if (!isMapLoaded) { + return; + } + + if (oldType.blockSolid || oldType.blockPathFind || newType.blockPathFind || newType.blockSolid) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, const Item*) +{ + if (!isMapLoaded) { + return; + } + + if (iType.blockSolid || iType.blockPathFind || iType.isGroundTile()) { + if (pos.z == getPosition().z) { + updateTileCache(tile, pos); + } + } +} + +void Creature::onCreatureAppear(Creature* creature, bool isLogin) +{ + if (creature == this) { + if (useCacheMap()) { + isMapLoaded = true; + updateMapCache(); + } + + if (isLogin) { + setLastPosition(getPosition()); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onRemoveCreature(Creature* creature, bool) +{ + onCreatureDisappear(creature, true); + if (creature == this) { + if (master && !master->isRemoved()) { + master->removeSummon(this); + } + } else if (isMapLoaded) { + if (creature->getPosition().z == getPosition().z) { + updateTileCache(creature->getTile(), creature->getPosition()); + } + } +} + +void Creature::onCreatureDisappear(const Creature* creature, bool isLogout) +{ + if (attackedCreature == creature) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(isLogout); + } + + if (followCreature == creature) { + setFollowCreature(nullptr); + onFollowCreatureDisappear(isLogout); + } +} + +void Creature::onChangeZone(ZoneType_t zone) +{ + if (attackedCreature && zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + onCreatureDisappear(attackedCreature, false); + } +} + +void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + if (creature == this) { + lastStep = OTSYS_TIME(); + lastStepCost = 1; + + if (!teleport) { + if (oldPos.z != newPos.z) { + //floor change extra cost + lastStepCost = 2; + } else if (Position::getDistanceX(newPos, oldPos) >= 1 && Position::getDistanceY(newPos, oldPos) >= 1) { + //diagonal extra cost + lastStepCost = 3; + } + } else { + stopEventWalk(); + } + + if (!summons.empty()) { + //check if any of our summons is out of range (+/- 2 floors or 30 tiles away) + std::forward_list despawnList; + for (Creature* summon : summons) { + const Position& pos = summon->getPosition(); + if (Position::getDistanceZ(newPos, pos) > 2 || (std::max(Position::getDistanceX(newPos, pos), Position::getDistanceY(newPos, pos)) > 30)) { + despawnList.push_front(summon); + } + } + + for (Creature* despawnCreature : despawnList) { + g_game.removeCreature(despawnCreature, true); + } + } + + if (newTile->getZone() != oldTile->getZone()) { + onChangeZone(getZone()); + } + + //update map cache + if (isMapLoaded) { + if (teleport || oldPos.z != newPos.z) { + updateMapCache(); + } else { + const Position& myPos = getPosition(); + + if (oldPos.y > newPos.y) { //north + //shift y south + for (int32_t y = mapWalkHeight - 1; --y >= 0;) { + memcpy(localMapCache[y + 1], localMapCache[y], sizeof(localMapCache[y])); + } + + //update 0 + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() - maxWalkCacheHeight, myPos.z); + updateTileCache(cacheTile, x, -maxWalkCacheHeight); + } + } else if (oldPos.y < newPos.y) { // south + //shift y north + for (int32_t y = 0; y <= mapWalkHeight - 2; ++y) { + memcpy(localMapCache[y], localMapCache[y + 1], sizeof(localMapCache[y])); + } + + //update mapWalkHeight - 1 + for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) { + Tile* cacheTile = g_game.map.getTile(myPos.getX() + x, myPos.getY() + maxWalkCacheHeight, myPos.z); + updateTileCache(cacheTile, x, maxWalkCacheHeight); + } + } + + if (oldPos.x < newPos.x) { // east + //shift y west + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = Position::getDistanceY(oldPos, newPos); + + if (dy < 0) { + endy += dy; + } else if (dy > 0) { + starty = dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = 0; x <= mapWalkWidth - 2; ++x) { + localMapCache[y][x] = localMapCache[y][x + 1]; + } + } + + //update mapWalkWidth - 1 + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + Tile* cacheTile = g_game.map.getTile(myPos.x + maxWalkCacheWidth, myPos.y + y, myPos.z); + updateTileCache(cacheTile, maxWalkCacheWidth, y); + } + } else if (oldPos.x > newPos.x) { // west + //shift y east + int32_t starty = 0; + int32_t endy = mapWalkHeight - 1; + int32_t dy = Position::getDistanceY(oldPos, newPos); + + if (dy < 0) { + endy += dy; + } else if (dy > 0) { + starty = dy; + } + + for (int32_t y = starty; y <= endy; ++y) { + for (int32_t x = mapWalkWidth - 1; --x >= 0;) { + localMapCache[y][x + 1] = localMapCache[y][x]; + } + } + + //update 0 + for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) { + Tile* cacheTile = g_game.map.getTile(myPos.x - maxWalkCacheWidth, myPos.y + y, myPos.z); + updateTileCache(cacheTile, -maxWalkCacheWidth, y); + } + } + + updateTileCache(oldTile, oldPos); + } + } + } else { + if (isMapLoaded) { + const Position& myPos = getPosition(); + + if (newPos.z == myPos.z) { + updateTileCache(newTile, newPos); + } + + if (oldPos.z == myPos.z) { + updateTileCache(oldTile, oldPos); + } + } + } + + if (creature == followCreature || (creature == this && followCreature)) { + if (hasFollowPath) { + isUpdatingPath = true; + // this updates following walking + if (lastWalkUpdate == 0 || OTSYS_TIME() - lastWalkUpdate >= 250) { + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + lastWalkUpdate = OTSYS_TIME(); + } + } + + if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { + onCreatureDisappear(followCreature, false); + } + } + + if (creature == attackedCreature || (creature == this && attackedCreature)) { + if (newPos.z != oldPos.z || !canSee(attackedCreature->getPosition())) { + onCreatureDisappear(attackedCreature, false); + } else { + if (hasExtraSwing()) { + //our target is moving lets see if we can get in hit + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + + if (newTile->getZone() != oldTile->getZone()) { + onAttackedCreatureChangeZone(attackedCreature->getZone()); + } + } + } +} + +void Creature::onDeath() +{ + bool lastHitUnjustified = false; + bool mostDamageUnjustified = false; + Creature* lastHitCreature = g_game.getCreatureByID(lastHitCreatureId); + Creature* lastHitCreatureMaster; + if (lastHitCreature) { + lastHitUnjustified = lastHitCreature->onKilledCreature(this); + lastHitCreatureMaster = lastHitCreature->getMaster(); + } else { + lastHitCreatureMaster = nullptr; + } + + Creature* mostDamageCreature = nullptr; + + const int64_t timeNow = OTSYS_TIME(); + const uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); + int32_t mostDamage = 0; + std::map experienceMap; + for (const auto& it : damageMap) { + if (Creature* attacker = g_game.getCreatureByID(it.first)) { + CountBlock_t cb = it.second; + if ((cb.total > mostDamage && (timeNow - cb.ticks <= inFightTicks))) { + mostDamage = cb.total; + mostDamageCreature = attacker; + } + + if (attacker != this) { + uint64_t gainExp = getGainedExperience(attacker); + if (Player* player = attacker->getPlayer()) { + Party* party = player->getParty(); + if (party && party->getLeader() && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + attacker = party->getLeader(); + } + } + + auto tmpIt = experienceMap.find(attacker); + if (tmpIt == experienceMap.end()) { + experienceMap[attacker] = gainExp; + } else { + tmpIt->second += gainExp; + } + } + } + } + + for (const auto& it : experienceMap) { + it.first->onGainExperience(it.second, this); + } + + if (mostDamageCreature) { + if (mostDamageCreature != lastHitCreature && mostDamageCreature != lastHitCreatureMaster) { + Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); + if (lastHitCreature != mostDamageCreatureMaster && (lastHitCreatureMaster == nullptr || mostDamageCreatureMaster != lastHitCreatureMaster)) { + mostDamageUnjustified = mostDamageCreature->onKilledCreature(this, false); + } + } + } + + bool droppedCorpse = dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + death(lastHitCreature); + + if (master) { + master->removeSummon(this); + } + + if (droppedCorpse) { + g_game.removeCreature(this, false); + } +} + +bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + Item* splash; + switch (getRace()) { + case RACE_VENOM: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); + break; + + case RACE_BLOOD: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); + break; + + default: + splash = nullptr; + break; + } + + Tile* tile = getTile(); + + if (splash) { + g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(splash); + } + + Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(corpse); + } + + //scripting event - onDeath + for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { + deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + if (corpse) { + dropLoot(corpse->getContainer(), lastHitCreature); + } + + return true; +} + +bool Creature::hasBeenAttacked(uint32_t attackerId) +{ + auto it = damageMap.find(attackerId); + if (it == damageMap.end()) { + return false; + } + return (OTSYS_TIME() - it->second.ticks) <= g_config.getNumber(ConfigManager::PZ_LOCKED); +} + +Item* Creature::getCorpse(Creature*, Creature*) +{ + return Item::CreateItem(getLookCorpse()); +} + +void Creature::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + int32_t oldHealth = health; + + if (healthChange > 0) { + health += std::min(healthChange, getMaxHealth() - health); + } else { + health = std::max(0, health + healthChange); + } + + if (sendHealthChange && oldHealth != health) { + g_game.addCreatureHealth(this); + } +} + +void Creature::changeMana(int32_t manaChange) +{ + if (manaChange > 0) { + mana += std::min(manaChange, getMaxMana() - mana); + } else { + mana = std::max(0, mana + manaChange); + } +} + +void Creature::gainHealth(Creature* healer, int32_t healthGain) +{ + changeHealth(healthGain); + if (healer) { + healer->onTargetCreatureGainHealth(this, healthGain); + } +} + +void Creature::drainHealth(Creature* attacker, int32_t damage) +{ + changeHealth(-damage, false); + + if (attacker) { + attacker->onAttackedCreatureDrainHealth(this, damage); + } +} + +void Creature::drainMana(Creature* attacker, int32_t manaLoss) +{ + onAttacked(); + changeMana(-manaLoss); + + if (attacker) { + addDamagePoints(attacker, manaLoss); + } +} + +BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false */, bool checkArmor /* = false */, bool /* field = false */) +{ + BlockType_t blockType = BLOCK_NONE; + + if (isImmune(combatType)) { + damage = 0; + blockType = BLOCK_IMMUNITY; + } else if (checkDefense || checkArmor) { + if (checkDefense && OTSYS_TIME() >= earliestDefendTime) { + damage -= getDefense(); + + earliestDefendTime = lastDefendTime + 2000; + lastDefendTime = OTSYS_TIME(); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + } + } + + if (checkArmor) { + if (damage > 0 && combatType == COMBAT_PHYSICALDAMAGE) { + damage -= getArmor(); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + } + + bool hasDefense = false; + + if (blockCount > 0) { + --blockCount; + hasDefense = true; + } + + if (hasDefense && blockType != BLOCK_NONE) { + onBlockHit(); + } + } + + if (attacker) { + attacker->onAttackedCreature(this); + attacker->onAttackedCreatureBlockHit(blockType); + } + + onAttacked(); + return blockType; +} + +bool Creature::setAttackedCreature(Creature* creature) +{ + if (creature) { + const Position& creaturePos = creature->getPosition(); + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + attackedCreature = nullptr; + return false; + } + + if (isSummon() && master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1) { + if (!monster->hasFollowPath && !monster->followCreature) { + return false; + } + } + } + } + + attackedCreature = creature; + onAttackedCreature(attackedCreature); + attackedCreature->onAttacked(); + } else { + attackedCreature = nullptr; + } + + for (Creature* summon : summons) { + summon->setAttackedCreature(creature); + } + return true; +} + +void Creature::getPathSearchParams(const Creature*, FindPathParams& fpp) const +{ + fpp.fullPathSearch = !hasFollowPath; + fpp.clearSight = true; + fpp.maxSearchDist = 12; + fpp.minTargetDist = 1; + fpp.maxTargetDist = 1; +} + +void Creature::goToFollowCreature() +{ + if (followCreature) { + FindPathParams fpp; + getPathSearchParams(followCreature, fpp); + + Monster* monster = getMonster(); + if (monster && !monster->getMaster() && (monster->isFleeing() || fpp.maxTargetDist > 1)) { + Direction dir = DIRECTION_NONE; + + if (monster->isFleeing()) { + monster->getDistanceStep(followCreature->getPosition(), dir, true); + } else { //maxTargetDist > 1 + if (!monster->getDistanceStep(followCreature->getPosition(), dir)) { + // if we can't get anything then let the A* calculate + listWalkDir.clear(); + if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + return; + } + } + + if (dir != DIRECTION_NONE) { + listWalkDir.clear(); + listWalkDir.push_front(dir); + + hasFollowPath = true; + startAutoWalk(listWalkDir); + } + } else { + listWalkDir.clear(); + if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) { + hasFollowPath = true; + startAutoWalk(listWalkDir); + } else { + hasFollowPath = false; + } + } + } + + onFollowCreatureComplete(followCreature); +} + +bool Creature::setFollowCreature(Creature* creature) +{ + if (creature) { + if (followCreature == creature) { + return true; + } + + const Position& creaturePos = creature->getPosition(); + if (creaturePos.z != getPosition().z || !canSee(creaturePos)) { + followCreature = nullptr; + return false; + } + + if (!listWalkDir.empty()) { + listWalkDir.clear(); + onWalkAborted(); + } + + hasFollowPath = false; + forceUpdateFollowPath = false; + followCreature = creature; + isUpdatingPath = true; + } else { + isUpdatingPath = false; + followCreature = nullptr; + } + + onFollowCreature(creature); + return true; +} + +double Creature::getDamageRatio(Creature* attacker) const +{ + uint32_t totalDamage = 0; + uint32_t attackerDamage = 0; + + for (const auto& it : damageMap) { + const CountBlock_t& cb = it.second; + totalDamage += cb.total; + if (it.first == attacker->getID()) { + attackerDamage += cb.total; + } + } + + if (totalDamage == 0) { + return 0; + } + + return (static_cast(attackerDamage) / totalDamage); +} + +uint64_t Creature::getGainedExperience(Creature* attacker) const +{ + return std::floor(getDamageRatio(attacker) * getLostExperience()); +} + +void Creature::addDamagePoints(Creature* attacker, int32_t damagePoints) +{ + if (damagePoints <= 0) { + return; + } + + uint32_t attackerId = attacker->id; + + auto it = damageMap.find(attackerId); + if (it == damageMap.end()) { + CountBlock_t cb; + cb.ticks = OTSYS_TIME(); + cb.total = damagePoints; + damageMap[attackerId] = cb; + } else { + it->second.total += damagePoints; + it->second.ticks = OTSYS_TIME(); + } + + lastHitCreatureId = attackerId; +} + +void Creature::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_PARALYZE && hasCondition(CONDITION_HASTE)) { + removeCondition(CONDITION_HASTE); + } else if (type == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + removeCondition(CONDITION_PARALYZE); + } +} + +void Creature::onAddCombatCondition(ConditionType_t) +{ + // +} + +void Creature::onEndCondition(ConditionType_t) +{ + // +} + +void Creature::onTickCondition(ConditionType_t type, bool& bRemove) +{ + const MagicField* field = getTile()->getFieldItem(); + if (!field) { + return; + } + + switch (type) { + case CONDITION_FIRE: + bRemove = (field->getCombatType() != COMBAT_FIREDAMAGE); + break; + case CONDITION_ENERGY: + bRemove = (field->getCombatType() != COMBAT_ENERGYDAMAGE); + break; + case CONDITION_POISON: + bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE); + break; + default: + break; + } +} + +void Creature::onCombatRemoveCondition(Condition* condition) +{ + removeCondition(condition); +} + +void Creature::onAttacked() +{ + // +} + +void Creature::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + target->addDamagePoints(this, points); +} + +bool Creature::onKilledCreature(Creature* target, bool) +{ + if (latestKillEvent == target->getID()) { + return false; + } + + latestKillEvent = target->getID(); + + if (master) { + master->onKilledCreature(target); + return false; + } + + //scripting event - onKill + const CreatureEventList& killEvents = getCreatureEvents(CREATURE_EVENT_KILL); + for (CreatureEvent* killEvent : killEvents) { + killEvent->executeOnKill(this, target); + } + return false; +} + +void Creature::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (gainExp == 0 || !master) { + return; + } + + gainExp /= 2; + master->onGainExperience(gainExp, target); + + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(gainExp)); +} + +void Creature::addSummon(Creature* creature) +{ + creature->setDropLoot(false); + creature->setLossSkill(false); + creature->setMaster(this); + creature->incrementReferenceCounter(); + summons.push_back(creature); +} + +void Creature::removeSummon(Creature* creature) +{ + auto cit = std::find(summons.begin(), summons.end(), creature); + if (cit != summons.end()) { + creature->setDropLoot(true); + creature->setLossSkill(true); + creature->setMaster(nullptr); + creature->decrementReferenceCounter(); + summons.erase(cit); + } +} + +bool Creature::addCondition(Condition* condition, bool force/* = false*/) +{ + if (condition == nullptr) { + return false; + } + + if (!force && condition->getType() == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceAddCondition, &g_game, getID(), condition))); + return false; + } + } + + Condition* prevCond = getCondition(condition->getType(), condition->getId(), condition->getSubId()); + if (prevCond) { + prevCond->addCondition(this, condition); + delete condition; + return true; + } + + if (condition->startCondition(this)) { + conditions.push_back(condition); + onAddCondition(condition->getType()); + return true; + } + + delete condition; + return false; +} + +bool Creature::addCombatCondition(Condition* condition) +{ + //Caution: condition variable could be deleted after the call to addCondition + ConditionType_t type = condition->getType(); + + if (!addCondition(condition)) { + return false; + } + + onAddCombatCondition(type); + return true; +} + +void Creature::removeCondition(ConditionType_t type, bool force/* = false*/) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->getType() != type) { + ++it; + continue; + } + + if (!force && type == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force/* = false*/) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->getType() != type || condition->getId() != conditionId) { + ++it; + continue; + } + + if (!force && type == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), type))); + return; + } + } + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } +} + +void Creature::removeCombatCondition(ConditionType_t type) +{ + std::vector removeConditions; + for (Condition* condition : conditions) { + if (condition->getType() == type) { + removeConditions.push_back(condition); + } + } + + for (Condition* condition : removeConditions) { + onCombatRemoveCondition(condition); + } +} + +void Creature::removeCondition(Condition* condition, bool force/* = false*/) +{ + auto it = std::find(conditions.begin(), conditions.end(), condition); + if (it == conditions.end()) { + return; + } + + if (!force && condition->getType() == CONDITION_PARALYZE) { + int64_t walkDelay = getWalkDelay(); + if (walkDelay > 0) { + g_scheduler.addEvent(createSchedulerTask(walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(), condition->getType()))); + return; + } + } + + conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; +} + +Condition* Creature::getCondition(ConditionType_t type) const +{ + for (Condition* condition : conditions) { + if (condition->getType() == type) { + return condition; + } + } + return nullptr; +} + +Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId/* = 0*/) const +{ + for (Condition* condition : conditions) { + if (condition->getType() == type && condition->getId() == conditionId && condition->getSubId() == subId) { + return condition; + } + } + return nullptr; +} + +void Creature::executeConditions(uint32_t interval) +{ + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (!condition->executeCondition(this, interval)) { + ConditionType_t type = condition->getType(); + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } else { + ++it; + } + } +} + +bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const +{ + if (isSuppress(type)) { + return false; + } + + int64_t timeNow = OTSYS_TIME(); + for (Condition* condition : conditions) { + if (condition->getType() != type || condition->getSubId() != subId) { + continue; + } + + if (condition->getEndTime() >= timeNow) { + return true; + } + } + return false; +} + +bool Creature::isImmune(CombatType_t type) const +{ + return hasBitSet(static_cast(type), getDamageImmunities()); +} + +bool Creature::isImmune(ConditionType_t type) const +{ + return hasBitSet(static_cast(type), getConditionImmunities()); +} + +bool Creature::isSuppress(ConditionType_t type) const +{ + return hasBitSet(static_cast(type), getConditionSuppressions()); +} + +int64_t Creature::getStepDuration(Direction dir) const +{ + int64_t stepDuration = getStepDuration(); + if ((dir & DIRECTION_DIAGONAL_MASK) != 0) { + stepDuration *= 3; + } + return stepDuration; +} + +int64_t Creature::getStepDuration() const +{ + if (isRemoved()) { + return 0; + } + + uint32_t groundSpeed; + int32_t stepSpeed = getStepSpeed(); + + Item* ground = tile->getGround(); + if (ground) { + groundSpeed = Item::items[ground->getID()].speed; + if (groundSpeed == 0) { + groundSpeed = 150; + } + } else { + groundSpeed = 150; + } + + double duration = std::floor(1000 * groundSpeed) / stepSpeed; + int64_t stepDuration = std::ceil(duration / 50) * 50; + + const Monster* monster = getMonster(); + if (monster && monster->isTargetNearby() && !monster->isFleeing() && !monster->getMaster()) { + stepDuration *= 3; + } + + return stepDuration; +} + +int64_t Creature::getEventStepTicks(bool onlyDelay) const +{ + int64_t ret = getWalkDelay(); + if (ret <= 0) { + int64_t stepDuration = getStepDuration(); + if (onlyDelay && stepDuration > 0) { + ret = 1; + } else { + ret = stepDuration * lastStepCost; + } + } + return ret; +} + +void Creature::getCreatureLight(LightInfo& light) const +{ + light = internalLight; +} + +void Creature::setNormalCreatureLight() +{ + internalLight.level = 0; + internalLight.color = 0; +} + +bool Creature::registerCreatureEvent(const std::string& name) +{ + CreatureEvent* event = g_creatureEvents->getEventByName(name); + if (!event) { + return false; + } + + CreatureEventType_t type = event->getEventType(); + if (hasEventRegistered(type)) { + for (CreatureEvent* creatureEvent : eventsList) { + if (creatureEvent == event) { + return false; + } + } + } else { + scriptEventsBitField |= static_cast(1) << type; + } + + eventsList.push_back(event); + return true; +} + +bool Creature::unregisterCreatureEvent(const std::string& name) +{ + CreatureEvent* event = g_creatureEvents->getEventByName(name); + if (!event) { + return false; + } + + CreatureEventType_t type = event->getEventType(); + if (!hasEventRegistered(type)) { + return false; + } + + bool resetTypeBit = true; + + auto it = eventsList.begin(), end = eventsList.end(); + while (it != end) { + CreatureEvent* curEvent = *it; + if (curEvent == event) { + it = eventsList.erase(it); + continue; + } + + if (curEvent->getEventType() == type) { + resetTypeBit = false; + } + ++it; + } + + if (resetTypeBit) { + scriptEventsBitField &= ~(static_cast(1) << type); + } + return true; +} + +CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) +{ + CreatureEventList tmpEventList; + + if (!hasEventRegistered(type)) { + return tmpEventList; + } + + for (CreatureEvent* creatureEvent : eventsList) { + if (creatureEvent->getEventType() == type) { + tmpEventList.push_back(creatureEvent); + } + } + + return tmpEventList; +} + +bool FrozenPathingConditionCall::isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const +{ + if (fpp.fullPathSearch) { + if (testPos.x > targetPos.x + fpp.maxTargetDist) { + return false; + } + + if (testPos.x < targetPos.x - fpp.maxTargetDist) { + return false; + } + + if (testPos.y > targetPos.y + fpp.maxTargetDist) { + return false; + } + + if (testPos.y < targetPos.y - fpp.maxTargetDist) { + return false; + } + } else { + int_fast32_t dx = Position::getOffsetX(startPos, targetPos); + + int32_t dxMax = (dx >= 0 ? fpp.maxTargetDist : 0); + if (testPos.x > targetPos.x + dxMax) { + return false; + } + + int32_t dxMin = (dx <= 0 ? fpp.maxTargetDist : 0); + if (testPos.x < targetPos.x - dxMin) { + return false; + } + + int_fast32_t dy = Position::getOffsetY(startPos, targetPos); + + int32_t dyMax = (dy >= 0 ? fpp.maxTargetDist : 0); + if (testPos.y > targetPos.y + dyMax) { + return false; + } + + int32_t dyMin = (dy <= 0 ? fpp.maxTargetDist : 0); + if (testPos.y < targetPos.y - dyMin) { + return false; + } + } + return true; +} + +bool FrozenPathingConditionCall::operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const +{ + if (!isInRange(startPos, testPos, fpp)) { + return false; + } + + if (fpp.clearSight && !g_game.isSightClear(testPos, targetPos, true)) { + return false; + } + + int32_t testDist = std::max(Position::getDistanceX(targetPos, testPos), Position::getDistanceY(targetPos, testPos)); + if (fpp.maxTargetDist == 1) { + if (testDist < fpp.minTargetDist || testDist > fpp.maxTargetDist) { + return false; + } + + return true; + } else if (testDist <= fpp.maxTargetDist) { + if (testDist < fpp.minTargetDist) { + return false; + } + + if (testDist == fpp.maxTargetDist) { + bestMatchDist = 0; + return true; + } else if (testDist > bestMatchDist) { + //not quite what we want, but the best so far + bestMatchDist = testDist; + return true; + } + } + return false; +} + +bool Creature::isInvisible() const +{ + return std::find_if(conditions.begin(), conditions.end(), [] (const Condition* condition) { + return condition->getType() == CONDITION_INVISIBLE; + }) != conditions.end(); +} + +bool Creature::getPathTo(const Position& targetPos, std::forward_list& dirList, const FindPathParams& fpp) const +{ + return g_game.map.getPathMatching(*this, dirList, FrozenPathingConditionCall(targetPos), fpp); +} + +bool Creature::getPathTo(const Position& targetPos, std::forward_list& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch /*= true*/, bool clearSight /*= true*/, int32_t maxSearchDist /*= 0*/) const +{ + FindPathParams fpp; + fpp.fullPathSearch = fullPathSearch; + fpp.maxSearchDist = maxSearchDist; + fpp.clearSight = clearSight; + fpp.minTargetDist = minTargetDist; + fpp.maxTargetDist = maxTargetDist; + return getPathTo(targetPos, dirList, fpp); +} diff --git a/src/creature.h b/src/creature.h new file mode 100644 index 0000000..39afb11 --- /dev/null +++ b/src/creature.h @@ -0,0 +1,557 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CREATURE_H_5363C04015254E298F84E6D59A139508 +#define FS_CREATURE_H_5363C04015254E298F84E6D59A139508 + +#include "map.h" +#include "position.h" +#include "condition.h" +#include "const.h" +#include "tile.h" +#include "enums.h" +#include "creatureevent.h" + +typedef std::list ConditionList; +typedef std::list CreatureEventList; + +enum slots_t : uint8_t { + CONST_SLOT_WHEREEVER = 0, + CONST_SLOT_HEAD = 1, + CONST_SLOT_NECKLACE = 2, + CONST_SLOT_BACKPACK = 3, + CONST_SLOT_ARMOR = 4, + CONST_SLOT_RIGHT = 5, + CONST_SLOT_LEFT = 6, + CONST_SLOT_LEGS = 7, + CONST_SLOT_FEET = 8, + CONST_SLOT_RING = 9, + CONST_SLOT_AMMO = 10, + + CONST_SLOT_FIRST = CONST_SLOT_HEAD, + CONST_SLOT_LAST = CONST_SLOT_AMMO, +}; + +struct FindPathParams { + bool fullPathSearch = true; + bool clearSight = true; + bool allowDiagonal = true; + bool keepDistance = false; + int32_t maxSearchDist = 0; + int32_t minTargetDist = -1; + int32_t maxTargetDist = -1; +}; + +class Map; +class Thing; +class Container; +class Player; +class Monster; +class Npc; +class Item; +class Tile; +class Combat; + +static constexpr int32_t EVENT_CREATURECOUNT = 10; +static constexpr int32_t EVENT_CREATURE_THINK_INTERVAL = 1000; +static constexpr int32_t EVENT_CHECK_CREATURE_INTERVAL = (EVENT_CREATURE_THINK_INTERVAL / EVENT_CREATURECOUNT); + +class FrozenPathingConditionCall +{ + public: + explicit FrozenPathingConditionCall(Position targetPos) : targetPos(std::move(targetPos)) {} + + bool operator()(const Position& startPos, const Position& testPos, + const FindPathParams& fpp, int32_t& bestMatchDist) const; + + bool isInRange(const Position& startPos, const Position& testPos, + const FindPathParams& fpp) const; + + protected: + Position targetPos; +}; + +////////////////////////////////////////////////////////////////////// +// Defines the Base class for all creatures and base functions which +// every creature has + +class Creature : virtual public Thing +{ + protected: + Creature(); + + public: + virtual ~Creature(); + + // non-copyable + Creature(const Creature&) = delete; + Creature& operator=(const Creature&) = delete; + + Creature* getCreature() final { + return this; + } + const Creature* getCreature() const final { + return this; + } + virtual Player* getPlayer() { + return nullptr; + } + virtual const Player* getPlayer() const { + return nullptr; + } + virtual Npc* getNpc() { + return nullptr; + } + virtual const Npc* getNpc() const { + return nullptr; + } + virtual Monster* getMonster() { + return nullptr; + } + virtual const Monster* getMonster() const { + return nullptr; + } + + virtual const std::string& getName() const = 0; + virtual const std::string& getNameDescription() const = 0; + + virtual void setID() = 0; + void setRemoved() { + isInternalRemoved = true; + } + + uint32_t getID() const { + return id; + } + virtual void removeList() = 0; + virtual void addList() = 0; + + virtual bool canSee(const Position& pos) const; + virtual bool canSeeCreature(const Creature* creature) const; + + virtual RaceType_t getRace() const { + return RACE_NONE; + } + virtual Skulls_t getSkull() const { + return skull; + } + virtual Skulls_t getSkullClient(const Creature* creature) const { + return creature->getSkull(); + } + void setSkull(Skulls_t newSkull); + Direction getDirection() const { + return direction; + } + void setDirection(Direction dir) { + direction = dir; + } + + int32_t getThrowRange() const final { + return 1; + } + bool isPushable() const override { + return getWalkDelay() <= 0; + } + bool isRemoved() const final { + return isInternalRemoved; + } + virtual bool canSeeInvisibility() const { + return false; + } + virtual bool isInGhostMode() const { + return false; + } + + int32_t getWalkDelay(Direction dir) const; + int32_t getWalkDelay() const; + int64_t getTimeSinceLastMove() const; + + int64_t getEventStepTicks(bool onlyDelay = false) const; + int64_t getStepDuration(Direction dir) const; + int64_t getStepDuration() const; + virtual int32_t getStepSpeed() const { + return getSpeed(); + } + int32_t getSpeed() const { + if (baseSpeed == 0) { + return 0; + } + + return (2 * (varSpeed + baseSpeed)) + 80; + } + void setSpeed(int32_t varSpeedDelta) { + int32_t oldSpeed = getSpeed(); + varSpeed += varSpeedDelta; + + if (getSpeed() <= 0) { + stopEventWalk(); + cancelNextWalk = true; + } else if (oldSpeed <= 0 && !listWalkDir.empty()) { + addEventWalk(); + } + } + + void setBaseSpeed(uint32_t newBaseSpeed) { + baseSpeed = newBaseSpeed; + } + uint32_t getBaseSpeed() const { + return baseSpeed; + } + + int32_t getHealth() const { + return health; + } + virtual int32_t getMaxHealth() const { + return healthMax; + } + uint32_t getMana() const { + return mana; + } + virtual uint32_t getMaxMana() const { + return 0; + } + + const Outfit_t getCurrentOutfit() const { + return currentOutfit; + } + void setCurrentOutfit(Outfit_t outfit) { + currentOutfit = outfit; + } + const Outfit_t getDefaultOutfit() const { + return defaultOutfit; + } + bool isInvisible() const; + ZoneType_t getZone() const { + return getTile()->getZone(); + } + + //walk functions + void startAutoWalk(const std::forward_list& listDir); + void addEventWalk(bool firstStep = false); + void stopEventWalk(); + virtual void goToFollowCreature(); + + //walk events + virtual void onWalk(Direction& dir); + virtual void onWalkAborted() {} + virtual void onWalkComplete() {} + + //follow functions + Creature* getFollowCreature() const { + return followCreature; + } + virtual bool setFollowCreature(Creature* creature); + + //follow events + virtual void onFollowCreature(const Creature*) {} + virtual void onFollowCreatureComplete(const Creature*) {} + + //combat functions + Creature* getAttackedCreature() { + return attackedCreature; + } + virtual bool setAttackedCreature(Creature* creature); + virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false); + + void setMaster(Creature* creature) { + master = creature; + } + bool isSummon() const { + return master != nullptr; + } + Creature* getMaster() const { + return master; + } + + void addSummon(Creature* creature); + void removeSummon(Creature* creature); + const std::list& getSummons() const { + return summons; + } + + virtual int32_t getArmor() const { + return 0; + } + virtual int32_t getDefense() { + return 0; + } + + bool addCondition(Condition* condition, bool force = false); + bool addCombatCondition(Condition* condition); + void removeCondition(ConditionType_t type, ConditionId_t conditionId, bool force = false); + void removeCondition(ConditionType_t type, bool force = false); + void removeCondition(Condition* condition, bool force = false); + void removeCombatCondition(ConditionType_t type); + Condition* getCondition(ConditionType_t type) const; + Condition* getCondition(ConditionType_t type, ConditionId_t conditionId, uint32_t subId = 0) const; + void executeConditions(uint32_t interval); + bool hasCondition(ConditionType_t type, uint32_t subId = 0) const; + virtual bool isImmune(ConditionType_t type) const; + virtual bool isImmune(CombatType_t type) const; + virtual bool isSuppress(ConditionType_t type) const; + virtual uint32_t getDamageImmunities() const { + return 0; + } + virtual uint32_t getConditionImmunities() const { + return 0; + } + virtual uint32_t getConditionSuppressions() const { + return 0; + } + virtual bool isAttackable() const { + return true; + } + + virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); + virtual void changeMana(int32_t manaChange); + + void gainHealth(Creature* attacker, int32_t healthGain); + virtual void drainHealth(Creature* attacker, int32_t damage); + virtual void drainMana(Creature* attacker, int32_t manaLoss); + + virtual bool challengeCreature(Creature*) { + return false; + } + virtual bool convinceCreature(Creature*) { + return false; + } + + void onDeath(); + virtual uint64_t getGainedExperience(Creature* attacker) const; + void addDamagePoints(Creature* attacker, int32_t damagePoints); + bool hasBeenAttacked(uint32_t attackerId); + + //combat event functions + virtual void onAddCondition(ConditionType_t type); + virtual void onAddCombatCondition(ConditionType_t type); + virtual void onEndCondition(ConditionType_t type); + void onTickCondition(ConditionType_t type, bool& bRemove); + virtual void onCombatRemoveCondition(Condition* condition); + virtual void onAttackedCreature(Creature*) {} + virtual void onAttacked(); + virtual void onAttackedCreatureDrainHealth(Creature* target, int32_t points); + virtual void onTargetCreatureGainHealth(Creature*, int32_t) {} + virtual bool onKilledCreature(Creature* target, bool lastHit = true); + virtual void onGainExperience(uint64_t gainExp, Creature* target); + virtual void onAttackedCreatureBlockHit(BlockType_t) {} + virtual void onBlockHit() {} + virtual void onChangeZone(ZoneType_t zone); + virtual void onAttackedCreatureChangeZone(ZoneType_t zone); + virtual void onIdleStatus(); + + virtual void getCreatureLight(LightInfo& light) const; + virtual void setNormalCreatureLight(); + void setCreatureLight(LightInfo light) { + internalLight = light; + } + + virtual void onThink(uint32_t interval); + void onAttacking(uint32_t interval); + virtual void onWalk(); + virtual bool getNextStep(Direction& dir, uint32_t& flags); + + void onAddTileItem(const Tile* tile, const Position& pos); + virtual void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType); + virtual void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item); + + virtual void onCreatureAppear(Creature* creature, bool isLogin); + virtual void onRemoveCreature(Creature* creature, bool isLogout); + virtual void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport); + + virtual void onAttackedCreatureDisappear(bool) {} + virtual void onFollowCreatureDisappear(bool) {} + + virtual void onCreatureSay(Creature*, SpeakClasses, const std::string&) {} + + virtual void onCreatureConvinced(const Creature*, const Creature*) {} + virtual void onPlacedCreature() {} + + virtual bool getCombatValues(int32_t&, int32_t&) { + return false; + } + + size_t getSummonCount() const { + return summons.size(); + } + void setDropLoot(bool lootDrop) { + this->lootDrop = lootDrop; + } + void setLossSkill(bool skillLoss) { + this->skillLoss = skillLoss; + } + + //creature script events + bool registerCreatureEvent(const std::string& name); + bool unregisterCreatureEvent(const std::string& name); + + Cylinder* getParent() const final { + return tile; + } + void setParent(Cylinder* cylinder) final { + tile = static_cast(cylinder); + position = tile->getPosition(); + } + + inline const Position& getPosition() const final { + return position; + } + + Tile* getTile() final { + return tile; + } + const Tile* getTile() const final { + return tile; + } + + int32_t getWalkCache(const Position& pos) const; + + const Position& getLastPosition() const { + return lastPosition; + } + void setLastPosition(Position newLastPos) { + lastPosition = newLastPos; + } + + static bool canSee(const Position& myPos, const Position& pos, int32_t viewRangeX, int32_t viewRangeY); + + double getDamageRatio(Creature* attacker) const; + + bool getPathTo(const Position& targetPos, std::forward_list& dirList, const FindPathParams& fpp) const; + bool getPathTo(const Position& targetPos, std::forward_list& dirList, int32_t minTargetDist, int32_t maxTargetDist, bool fullPathSearch = true, bool clearSight = true, int32_t maxSearchDist = 0) const; + + void incrementReferenceCounter() { + ++referenceCounter; + } + void decrementReferenceCounter() { + if (--referenceCounter == 0) { + delete this; + } + } + + protected: + virtual bool useCacheMap() const { + return false; + } + + struct CountBlock_t { + int32_t total; + int64_t ticks; + }; + + static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; + static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; + static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; + static constexpr int32_t maxWalkCacheHeight = (mapWalkHeight - 1) / 2; + + Position position; + + typedef std::map CountMap; + CountMap damageMap; + + std::list summons; + CreatureEventList eventsList; + ConditionList conditions; + + std::forward_list listWalkDir; + + Tile* tile = nullptr; + Creature* attackedCreature = nullptr; + Creature* master = nullptr; + Creature* followCreature = nullptr; + + int64_t earliestDefendTime = 0; + int64_t lastDefendTime = 0; + + uint64_t lastWalkUpdate = 0; + uint64_t lastStep = 0; + uint32_t referenceCounter = 0; + uint32_t id = 0; + uint32_t scriptEventsBitField = 0; + uint32_t eventWalk = 0; + uint32_t walkUpdateTicks = 0; + uint32_t lastHitCreatureId = 0; + uint32_t blockCount = 0; + uint32_t blockTicks = 0; + uint32_t lastStepCost = 1; + uint32_t baseSpeed = 70; + uint32_t mana = 0; + uint32_t latestKillEvent = 0; + int32_t varSpeed = 0; + int32_t health = 1000; + int32_t healthMax = 1000; + + Outfit_t currentOutfit; + Outfit_t defaultOutfit; + + Position lastPosition; + LightInfo internalLight; + + Direction direction = DIRECTION_SOUTH; + Skulls_t skull = SKULL_NONE; + + bool localMapCache[mapWalkHeight][mapWalkWidth] = {{ false }}; + bool isInternalRemoved = false; + bool isMapLoaded = false; + bool isUpdatingPath = false; + bool creatureCheck = false; + bool inCheckCreaturesVector = false; + bool skillLoss = true; + bool lootDrop = true; + bool cancelNextWalk = false; + bool hasFollowPath = false; + bool forceUpdateFollowPath = false; + + //creature script events + bool hasEventRegistered(CreatureEventType_t event) const { + return (0 != (scriptEventsBitField & (static_cast(1) << event))); + } + CreatureEventList getCreatureEvents(CreatureEventType_t type); + + void updateMapCache(); + void updateTileCache(const Tile* tile, int32_t dx, int32_t dy); + void updateTileCache(const Tile* tile, const Position& pos); + void onCreatureDisappear(const Creature* creature, bool isLogout); + virtual void doAttacking(uint32_t) {} + virtual bool hasExtraSwing() { + return false; + } + + virtual uint64_t getLostExperience() const { + return 0; + } + virtual void dropLoot(Container*, Creature*) {} + virtual uint16_t getLookCorpse() const { + return 0; + } + virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; + virtual void death(Creature*) {} + virtual bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified); + virtual Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature); + + friend class Game; + friend class Map; + friend class LuaScriptInterface; + friend class Combat; +}; + +#endif diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp new file mode 100644 index 0000000..990fa64 --- /dev/null +++ b/src/creatureevent.cpp @@ -0,0 +1,434 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "creatureevent.h" +#include "tools.h" +#include "player.h" + +CreatureEvents::CreatureEvents() : + scriptInterface("CreatureScript Interface") +{ + scriptInterface.initState(); +} + +CreatureEvents::~CreatureEvents() +{ + for (const auto& it : creatureEvents) { + delete it.second; + } +} + +void CreatureEvents::clear() +{ + //clear creature events + for (const auto& it : creatureEvents) { + it.second->clearEvent(); + } + + //clear lua state + scriptInterface.reInitState(); +} + +LuaScriptInterface& CreatureEvents::getScriptInterface() +{ + return scriptInterface; +} + +std::string CreatureEvents::getScriptBaseName() const +{ + return "creaturescripts"; +} + +Event* CreatureEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "event") != 0) { + return nullptr; + } + return new CreatureEvent(&scriptInterface); +} + +bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) +{ + CreatureEvent* creatureEvent = static_cast(event); //event is guaranteed to be a CreatureEvent + if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { + std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl; + return false; + } + + CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); + if (oldEvent) { + //if there was an event with the same that is not loaded + //(happens when realoading), it is reused + if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { + oldEvent->copyEvent(creatureEvent); + } + + return false; + } else { + //if not, register it normally + creatureEvents[creatureEvent->getName()] = creatureEvent; + return true; + } +} + +CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/) +{ + auto it = creatureEvents.find(name); + if (it != creatureEvents.end()) { + if (!forceLoaded || it->second->isLoaded()) { + return it->second; + } + } + return nullptr; +} + +bool CreatureEvents::playerLogin(Player* player) const +{ + //fire global event if is registered + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_LOGIN) { + if (!it.second->executeOnLogin(player)) { + return false; + } + } + } + return true; +} + +bool CreatureEvents::playerLogout(Player* player) const +{ + //fire global event if is registered + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_LOGOUT) { + if (!it.second->executeOnLogout(player)) { + return false; + } + } + } + return true; +} + +bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_ADVANCE) { + if (!it.second->executeAdvance(player, skill, oldLevel, newLevel)) { + return false; + } + } + } + return true; +} + +///////////////////////////////////// + +CreatureEvent::CreatureEvent(LuaScriptInterface* interface) : + Event(interface), type(CREATURE_EVENT_NONE), loaded(false) {} + +bool CreatureEvent::configureEvent(const pugi::xml_node& node) +{ + // Name that will be used in monster xml files and + // lua function to register events to reference this event + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - CreatureEvent::configureEvent] Missing name for creature event" << std::endl; + return false; + } + + eventName = nameAttribute.as_string(); + + pugi::xml_attribute typeAttribute = node.attribute("type"); + if (!typeAttribute) { + std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName << std::endl; + return false; + } + + std::string tmpStr = asLowerCaseString(typeAttribute.as_string()); + if (tmpStr == "login") { + type = CREATURE_EVENT_LOGIN; + } else if (tmpStr == "logout") { + type = CREATURE_EVENT_LOGOUT; + } else if (tmpStr == "think") { + type = CREATURE_EVENT_THINK; + } else if (tmpStr == "preparedeath") { + type = CREATURE_EVENT_PREPAREDEATH; + } else if (tmpStr == "death") { + type = CREATURE_EVENT_DEATH; + } else if (tmpStr == "kill") { + type = CREATURE_EVENT_KILL; + } else if (tmpStr == "advance") { + type = CREATURE_EVENT_ADVANCE; + } else if (tmpStr == "extendedopcode") { + type = CREATURE_EVENT_EXTENDED_OPCODE; + } else { + std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName << std::endl; + return false; + } + + loaded = true; + return true; +} + +std::string CreatureEvent::getScriptEventName() const +{ + //Depending on the type script event name is different + switch (type) { + case CREATURE_EVENT_LOGIN: + return "onLogin"; + + case CREATURE_EVENT_LOGOUT: + return "onLogout"; + + case CREATURE_EVENT_THINK: + return "onThink"; + + case CREATURE_EVENT_PREPAREDEATH: + return "onPrepareDeath"; + + case CREATURE_EVENT_DEATH: + return "onDeath"; + + case CREATURE_EVENT_KILL: + return "onKill"; + + case CREATURE_EVENT_ADVANCE: + return "onAdvance"; + + case CREATURE_EVENT_EXTENDED_OPCODE: + return "onExtendedOpcode"; + + case CREATURE_EVENT_NONE: + default: + return std::string(); + } +} + +void CreatureEvent::copyEvent(CreatureEvent* creatureEvent) +{ + scriptId = creatureEvent->scriptId; + scriptInterface = creatureEvent->scriptInterface; + scripted = creatureEvent->scripted; + loaded = creatureEvent->loaded; +} + +void CreatureEvent::clearEvent() +{ + scriptId = 0; + scriptInterface = nullptr; + scripted = false; + loaded = false; +} + +bool CreatureEvent::executeOnLogin(Player* player) +{ + //onLogin(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnLogin] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + return scriptInterface->callFunction(1); +} + +bool CreatureEvent::executeOnLogout(Player* player) +{ + //onLogout(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnLogout] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + return scriptInterface->callFunction(1); +} + +bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) +{ + //onThink(creature, interval) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnThink] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + lua_pushnumber(L, interval); + + return scriptInterface->callFunction(2); +} + +bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer) +{ + //onPrepareDeath(creature, killer) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (killer) { + LuaScriptInterface::pushUserdata(L, killer); + LuaScriptInterface::setCreatureMetatable(L, -1, killer); + } else { + lua_pushnil(L); + } + + return scriptInterface->callFunction(2); +} + +bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + //onDeath(creature, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnDeath] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushThing(L, corpse); + + if (killer) { + LuaScriptInterface::pushUserdata(L, killer); + LuaScriptInterface::setCreatureMetatable(L, -1, killer); + } else { + lua_pushnil(L); + } + + if (mostDamageKiller) { + LuaScriptInterface::pushUserdata(L, mostDamageKiller); + LuaScriptInterface::setCreatureMetatable(L, -1, mostDamageKiller); + } else { + lua_pushnil(L); + } + + LuaScriptInterface::pushBoolean(L, lastHitUnjustified); + LuaScriptInterface::pushBoolean(L, mostDamageUnjustified); + + return scriptInterface->callFunction(6); +} + +bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel, + uint32_t newLevel) +{ + //onAdvance(player, skill, oldLevel, newLevel) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeAdvance] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + lua_pushnumber(L, static_cast(skill)); + lua_pushnumber(L, oldLevel); + lua_pushnumber(L, newLevel); + + return scriptInterface->callFunction(4); +} + +void CreatureEvent::executeOnKill(Creature* creature, Creature* target) +{ + //onKill(creature, target) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + scriptInterface->callVoidFunction(2); +} + +void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer) +{ + //onExtendedOpcode(player, opcode, buffer) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeExtendedOpcode] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, opcode); + LuaScriptInterface::pushString(L, buffer); + + scriptInterface->callVoidFunction(3); +} diff --git a/src/creatureevent.h b/src/creatureevent.h new file mode 100644 index 0000000..7617c46 --- /dev/null +++ b/src/creatureevent.h @@ -0,0 +1,111 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 +#define FS_CREATUREEVENT_H_73FCAF4608CB41399D53C919316646A9 + +#include "luascript.h" +#include "baseevents.h" +#include "enums.h" + +enum CreatureEventType_t { + CREATURE_EVENT_NONE, + CREATURE_EVENT_LOGIN, + CREATURE_EVENT_LOGOUT, + CREATURE_EVENT_THINK, + CREATURE_EVENT_PREPAREDEATH, + CREATURE_EVENT_DEATH, + CREATURE_EVENT_KILL, + CREATURE_EVENT_ADVANCE, + CREATURE_EVENT_EXTENDED_OPCODE, // otclient additional network opcodes +}; + +class CreatureEvent; + +class CreatureEvents final : public BaseEvents +{ + public: + CreatureEvents(); + ~CreatureEvents(); + + // non-copyable + CreatureEvents(const CreatureEvents&) = delete; + CreatureEvents& operator=(const CreatureEvents&) = delete; + + // global events + bool playerLogin(Player* player) const; + bool playerLogout(Player* player) const; + bool playerAdvance(Player* player, skills_t, uint32_t, uint32_t); + + CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); + + protected: + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + void clear() final; + + //creature events + typedef std::map CreatureEventList; + CreatureEventList creatureEvents; + + LuaScriptInterface scriptInterface; +}; + +class CreatureEvent final : public Event +{ + public: + explicit CreatureEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) final; + + CreatureEventType_t getEventType() const { + return type; + } + const std::string& getName() const { + return eventName; + } + bool isLoaded() const { + return loaded; + } + + void clearEvent(); + void copyEvent(CreatureEvent* creatureEvent); + + //scripting + bool executeOnLogin(Player* player); + bool executeOnLogout(Player* player); + bool executeOnThink(Creature* creature, uint32_t interval); + bool executeOnPrepareDeath(Creature* creature, Creature* killer); + bool executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified); + void executeOnKill(Creature* creature, Creature* target); + bool executeAdvance(Player* player, skills_t, uint32_t, uint32_t); + void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); + // + + protected: + std::string getScriptEventName() const final; + + std::string eventName; + CreatureEventType_t type; + bool loaded; +}; + +#endif diff --git a/src/cylinder.cpp b/src/cylinder.cpp new file mode 100644 index 0000000..9e9e289 --- /dev/null +++ b/src/cylinder.cpp @@ -0,0 +1,69 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "cylinder.h" + +VirtualCylinder* VirtualCylinder::virtualCylinder = new VirtualCylinder; + +int32_t Cylinder::getThingIndex(const Thing*) const +{ + return -1; +} + +size_t Cylinder::getFirstIndex() const +{ + return 0; +} + +size_t Cylinder::getLastIndex() const +{ + return 0; +} + +uint32_t Cylinder::getItemTypeCount(uint16_t, int32_t) const +{ + return 0; +} + +std::map& Cylinder::getAllItemTypeCount(std::map& countMap) const +{ + return countMap; +} + +Thing* Cylinder::getThing(size_t) const +{ + return nullptr; +} + +void Cylinder::internalAddThing(Thing*) +{ + // +} + +void Cylinder::internalAddThing(uint32_t, Thing*) +{ + // +} + +void Cylinder::startDecaying() +{ + // +} diff --git a/src/cylinder.h b/src/cylinder.h new file mode 100644 index 0000000..b31765f --- /dev/null +++ b/src/cylinder.h @@ -0,0 +1,250 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 +#define FS_CYLINDER_H_54BBCEB2A5B7415DAD837E4D58115150 + +#include "enums.h" +#include "thing.h" + +class Item; +class Creature; + +static constexpr int32_t INDEX_WHEREEVER = -1; + +enum cylinderflags_t { + FLAG_NOLIMIT = 1 << 0, //Bypass limits like capacity/container limits, blocking items/creatures etc. + FLAG_IGNOREBLOCKITEM = 1 << 1, //Bypass movable blocking item checks + FLAG_IGNOREBLOCKCREATURE = 1 << 2, //Bypass creature checks + FLAG_CHILDISOWNER = 1 << 3, //Used by containers to query capacity of the carrier (player) + FLAG_PATHFINDING = 1 << 4, //An additional check is done for floor changing/teleport items + FLAG_IGNOREFIELDDAMAGE = 1 << 5, //Bypass field damage checks + FLAG_IGNORENOTMOVEABLE = 1 << 6, //Bypass check for mobility + FLAG_IGNOREAUTOSTACK = 1 << 7, //queryDestination will not try to stack items together + FLAG_PLACECHECK = 1 << 8, //Special check for placing the monster +}; + +enum cylinderlink_t { + LINK_OWNER, + LINK_PARENT, + LINK_TOPPARENT, + LINK_NEAR, +}; + +class Cylinder : virtual public Thing +{ + public: + /** + * Query if the cylinder can add an object + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param thing the object to move/add + * \param count is the amount that we want to move/add + * \param flags if FLAG_CHILDISOWNER if set the query is from a child-cylinder (check cap etc.) + * if FLAG_NOLIMIT is set blocking items/container limits is ignored + * \param actor the creature trying to add the thing + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const = 0; + + /** + * Query the cylinder how much it can accept + * \param index points to the destination index (inventory slot/container position) + * -1 is a internal value and means add to a empty position, with no destItem + * \param thing the object to move/add + * \param count is the amount that we want to move/add + * \param maxQueryCount is the max amount that the cylinder can accept + * \param flags optional flags to modify the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const = 0; + + /** + * Query if the cylinder can remove an object + * \param thing the object to move/remove + * \param count is the amount that we want to remove + * \param flags optional flags to modify the default behaviour + * \returns ReturnValue holds the return value + */ + virtual ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const = 0; + + /** + * Query the destination cylinder + * \param index points to the destination index (inventory slot/container position), + * -1 is a internal value and means add to a empty position, with no destItem + * this method can change the index to point to the new cylinder index + * \param destItem is the destination object + * \param flags optional flags to modify the default behaviour + * this method can modify the flags + * \returns Cylinder returns the destination cylinder + */ + virtual Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) = 0; + + /** + * Add the object to the cylinder + * \param thing is the object to add + */ + virtual void addThing(Thing* thing) = 0; + + /** + * Add the object to the cylinder + * \param index points to the destination index (inventory slot/container position) + * \param thing is the object to add + */ + virtual void addThing(int32_t index, Thing* thing) = 0; + + /** + * Update the item count or type for an object + * \param thing is the object to update + * \param itemId is the new item id + * \param count is the new count value + */ + virtual void updateThing(Thing* thing, uint16_t itemId, uint32_t count) = 0; + + /** + * Replace an object with a new + * \param index is the position to change (inventory slot/container position) + * \param thing is the object to update + */ + virtual void replaceThing(uint32_t index, Thing* thing) = 0; + + /** + * Remove an object + * \param thing is the object to delete + * \param count is the new count value + */ + virtual void removeThing(Thing* thing, uint32_t count) = 0; + + /** + * Is sent after an operation (move/add) to update internal values + * \param thing is the object that has been added + * \param index is the objects new index value + * \param link holds the relation the object has to the cylinder + */ + virtual void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Is sent after an operation (move/remove) to update internal values + * \param thing is the object that has been removed + * \param index is the previous index of the removed object + * \param link holds the relation the object has to the cylinder + */ + virtual void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) = 0; + + /** + * Gets the index of an object + * \param thing the object to get the index value from + * \returns the index of the object, returns -1 if not found + */ + virtual int32_t getThingIndex(const Thing* thing) const; + + /** + * Returns the first index + * \returns the first index, if not implemented 0 is returned + */ + virtual size_t getFirstIndex() const; + + /** + * Returns the last index + * \returns the last index, if not implemented 0 is returned + */ + virtual size_t getLastIndex() const; + + /** + * Gets the object based on index + * \returns the object, returns nullptr if not found + */ + virtual Thing* getThing(size_t index) const; + + /** + * Get the amount of items of a certain type + * \param itemId is the item type to the get the count of + * \param subType is the extra type an item can have such as charges/fluidtype, -1 means not used + * \returns the amount of items of the asked item type + */ + virtual uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const; + + /** + * Get the amount of items of a all types + * \param countMap a map to put the itemID:count mapping in + * \returns a map mapping item id to count (same as first argument) + */ + virtual std::map& getAllItemTypeCount(std::map& countMap) const; + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + */ + virtual void internalAddThing(Thing* thing); + + /** + * Adds an object to the cylinder without sending to the client(s) + * \param thing is the object to add + * \param index points to the destination index (inventory slot/container position) + */ + virtual void internalAddThing(uint32_t index, Thing* thing); + + virtual void startDecaying(); +}; + +class VirtualCylinder final : public Cylinder +{ + public: + static VirtualCylinder* virtualCylinder; + + virtual ReturnValue queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature* = nullptr) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual ReturnValue queryRemove(const Thing&, uint32_t, uint32_t) const override { + return RETURNVALUE_NOTPOSSIBLE; + } + virtual Cylinder* queryDestination(int32_t&, const Thing&, Item**, uint32_t&) override { + return nullptr; + } + + virtual void addThing(Thing*) override {} + virtual void addThing(int32_t, Thing*) override {} + virtual void updateThing(Thing*, uint16_t, uint32_t) override {} + virtual void replaceThing(uint32_t, Thing*) override {} + virtual void removeThing(Thing*, uint32_t) override {} + + virtual void postAddNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + virtual void postRemoveNotification(Thing*, const Cylinder*, int32_t, cylinderlink_t = LINK_OWNER) override {} + + bool isPushable() const override { + return false; + } + int32_t getThrowRange() const override { + return 1; + } + std::string getDescription(int32_t) const override { + return {}; + } + bool isRemoved() const override { + return false; + } +}; + +#endif diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 0000000..5079e08 --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,295 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "database.h" + +#include + +extern ConfigManager g_config; + +Database::~Database() +{ + if (handle != nullptr) { + mysql_close(handle); + } +} + +bool Database::connect() +{ + // connection handle initialization + handle = mysql_init(nullptr); + if (!handle) { + std::cout << std::endl << "Failed to initialize MySQL connection handle." << std::endl; + return false; + } + + // automatic reconnect + bool reconnect = true; + mysql_options(handle, MYSQL_OPT_RECONNECT, &reconnect); + + // connects to database + if (!mysql_real_connect(handle, g_config.getString(ConfigManager::MYSQL_HOST).c_str(), g_config.getString(ConfigManager::MYSQL_USER).c_str(), g_config.getString(ConfigManager::MYSQL_PASS).c_str(), g_config.getString(ConfigManager::MYSQL_DB).c_str(), g_config.getNumber(ConfigManager::SQL_PORT), g_config.getString(ConfigManager::MYSQL_SOCK).c_str(), 0)) { + std::cout << std::endl << "MySQL Error Message: " << mysql_error(handle) << std::endl; + return false; + } + + DBResult_ptr result = storeQuery("SHOW VARIABLES LIKE 'max_allowed_packet'"); + if (result) { + maxPacketSize = result->getNumber("Value"); + } + return true; +} + +bool Database::beginTransaction() +{ + if (!executeQuery("BEGIN")) { + return false; + } + + databaseLock.lock(); + return true; +} + +bool Database::rollback() +{ + if (mysql_rollback(handle) != 0) { + std::cout << "[Error - mysql_rollback] Message: " << mysql_error(handle) << std::endl; + databaseLock.unlock(); + return false; + } + + databaseLock.unlock(); + return true; +} + +bool Database::commit() +{ + if (mysql_commit(handle) != 0) { + std::cout << "[Error - mysql_commit] Message: " << mysql_error(handle) << std::endl; + databaseLock.unlock(); + return false; + } + + databaseLock.unlock(); + return true; +} + +bool Database::executeQuery(const std::string& query) +{ + bool success = true; + + // executes the query + databaseLock.lock(); + + while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { + std::cout << "[Error - mysql_real_query] Query: " << query.substr(0, 256) << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + success = false; + break; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + MYSQL_RES* m_res = mysql_store_result(handle); + databaseLock.unlock(); + + if (m_res) { + mysql_free_result(m_res); + } + + return success; +} + +DBResult_ptr Database::storeQuery(const std::string& query) +{ + databaseLock.lock(); + + retry: + while (mysql_real_query(handle, query.c_str(), query.length()) != 0) { + std::cout << "[Error - mysql_real_query] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + break; + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // we should call that every time as someone would call executeQuery('SELECT...') + // as it is described in MySQL manual: "it doesn't hurt" :P + MYSQL_RES* res = mysql_store_result(handle); + if (res == nullptr) { + std::cout << "[Error - mysql_store_result] Query: " << query << std::endl << "Message: " << mysql_error(handle) << std::endl; + auto error = mysql_errno(handle); + if (error != CR_SERVER_LOST && error != CR_SERVER_GONE_ERROR && error != CR_CONN_HOST_ERROR && error != 1053/*ER_SERVER_SHUTDOWN*/ && error != CR_CONNECTION_ERROR) { + databaseLock.unlock(); + return nullptr; + } + goto retry; + } + databaseLock.unlock(); + + // retrieving results of query + DBResult_ptr result = std::make_shared(res); + if (!result->hasNext()) { + return nullptr; + } + return result; +} + +std::string Database::escapeString(const std::string& s) const +{ + return escapeBlob(s.c_str(), s.length()); +} + +std::string Database::escapeBlob(const char* s, uint32_t length) const +{ + // the worst case is 2n + 1 + size_t maxLength = (length * 2) + 1; + + std::string escaped; + escaped.reserve(maxLength + 2); + escaped.push_back('\''); + + if (length != 0) { + char* output = new char[maxLength]; + mysql_real_escape_string(handle, output, s, length); + escaped.append(output); + delete[] output; + } + + escaped.push_back('\''); + return escaped; +} + +DBResult::DBResult(MYSQL_RES* res) +{ + handle = res; + + size_t i = 0; + + MYSQL_FIELD* field = mysql_fetch_field(handle); + while (field) { + listNames[field->name] = i++; + field = mysql_fetch_field(handle); + } + + row = mysql_fetch_row(handle); +} + +DBResult::~DBResult() +{ + mysql_free_result(handle); +} + +std::string DBResult::getString(const std::string& s) const +{ + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getString] Column '" << s << "' does not exist in result set." << std::endl; + return std::string(); + } + + if (row[it->second] == nullptr) { + return std::string(); + } + + return std::string(row[it->second]); +} + +const char* DBResult::getStream(const std::string& s, unsigned long& size) const +{ + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getStream] Column '" << s << "' doesn't exist in the result set" << std::endl; + size = 0; + return nullptr; + } + + if (row[it->second] == nullptr) { + size = 0; + return nullptr; + } + + size = mysql_fetch_lengths(handle)[it->second]; + return row[it->second]; +} + +bool DBResult::hasNext() const +{ + return row != nullptr; +} + +bool DBResult::next() +{ + row = mysql_fetch_row(handle); + return row != nullptr; +} + +DBInsert::DBInsert(std::string query) : query(std::move(query)) +{ + this->length = this->query.length(); +} + +bool DBInsert::addRow(const std::string& row) +{ + // adds new row to buffer + const size_t rowLength = row.length(); + length += rowLength; + if (length > Database::getInstance()->getMaxPacketSize() && !execute()) { + return false; + } + + if (values.empty()) { + values.reserve(rowLength + 2); + values.push_back('('); + values.append(row); + values.push_back(')'); + } else { + values.reserve(values.length() + rowLength + 3); + values.push_back(','); + values.push_back('('); + values.append(row); + values.push_back(')'); + } + return true; +} + +bool DBInsert::addRow(std::ostringstream& row) +{ + bool ret = addRow(row.str()); + row.str(std::string()); + return ret; +} + +bool DBInsert::execute() +{ + if (values.empty()) { + return true; + } + + // executes buffer + bool res = Database::getInstance()->executeQuery(query + values); + values.clear(); + length = query.length(); + return res; +} diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000..f9ccf57 --- /dev/null +++ b/src/database.h @@ -0,0 +1,243 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 +#define FS_DATABASE_H_A484B0CDFDE542838F506DCE3D40C693 + +#include + +#include + +class DBResult; +typedef std::shared_ptr DBResult_ptr; + +class Database +{ + public: + Database() = default; + ~Database(); + + // non-copyable + Database(const Database&) = delete; + Database& operator=(const Database&) = delete; + + /** + * Singleton implementation. + * + * @return database connection handler singleton + */ + static Database* getInstance() + { + static Database instance; + return &instance; + } + + /** + * Connects to the database + * + * @return true on successful connection, false on error + */ + bool connect(); + + /** + * Executes command. + * + * Executes query which doesn't generates results (eg. INSERT, UPDATE, DELETE...). + * + * @param query command + * @return true on success, false on error + */ + bool executeQuery(const std::string& query); + + /** + * Queries database. + * + * Executes query which generates results (mostly SELECT). + * + * @return results object (nullptr on error) + */ + DBResult_ptr storeQuery(const std::string& query); + + /** + * Escapes string for query. + * + * Prepares string to fit SQL queries including quoting it. + * + * @param s string to be escaped + * @return quoted string + */ + std::string escapeString(const std::string& s) const; + + /** + * Escapes binary stream for query. + * + * Prepares binary stream to fit SQL queries. + * + * @param s binary stream + * @param length stream length + * @return quoted string + */ + std::string escapeBlob(const char* s, uint32_t length) const; + + /** + * Retrieve id of last inserted row + * + * @return id on success, 0 if last query did not result on any rows with auto_increment keys + */ + uint64_t getLastInsertId() const { + return static_cast(mysql_insert_id(handle)); + } + + /** + * Get database engine version + * + * @return the database engine version + */ + static const char* getClientVersion() { + return mysql_get_client_info(); + } + + uint64_t getMaxPacketSize() const { + return maxPacketSize; + } + + protected: + /** + * Transaction related methods. + * + * Methods for starting, commiting and rolling back transaction. Each of the returns boolean value. + * + * @return true on success, false on error + */ + bool beginTransaction(); + bool rollback(); + bool commit(); + + private: + MYSQL* handle = nullptr; + std::recursive_mutex databaseLock; + uint64_t maxPacketSize = 1048576; + + friend class DBTransaction; +}; + +class DBResult +{ + public: + explicit DBResult(MYSQL_RES* res); + ~DBResult(); + + // non-copyable + DBResult(const DBResult&) = delete; + DBResult& operator=(const DBResult&) = delete; + + template + T getNumber(const std::string& s) const + { + auto it = listNames.find(s); + if (it == listNames.end()) { + std::cout << "[Error - DBResult::getNumber] Column '" << s << "' doesn't exist in the result set" << std::endl; + return static_cast(0); + } + + if (row[it->second] == nullptr) { + return static_cast(0); + } + + T data; + try { + data = boost::lexical_cast(row[it->second]); + } catch (boost::bad_lexical_cast&) { + data = 0; + } + return data; + } + + std::string getString(const std::string& s) const; + const char* getStream(const std::string& s, unsigned long& size) const; + + bool hasNext() const; + bool next(); + + private: + MYSQL_RES* handle; + MYSQL_ROW row; + + std::map listNames; + + friend class Database; +}; + +/** + * INSERT statement. + */ +class DBInsert +{ + public: + explicit DBInsert(std::string query); + bool addRow(const std::string& row); + bool addRow(std::ostringstream& row); + bool execute(); + + protected: + std::string query; + std::string values; + size_t length; +}; + +class DBTransaction +{ + public: + constexpr DBTransaction() = default; + + ~DBTransaction() { + if (state == STATE_START) { + Database::getInstance()->rollback(); + } + } + + // non-copyable + DBTransaction(const DBTransaction&) = delete; + DBTransaction& operator=(const DBTransaction&) = delete; + + bool begin() { + state = STATE_START; + return Database::getInstance()->beginTransaction(); + } + + bool commit() { + if (state != STATE_START) { + return false; + } + + state = STEATE_COMMIT; + return Database::getInstance()->commit(); + } + + private: + enum TransactionStates_t { + STATE_NO_START, + STATE_START, + STEATE_COMMIT, + }; + + TransactionStates_t state = STATE_NO_START; +}; + +#endif diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp new file mode 100644 index 0000000..07f2325 --- /dev/null +++ b/src/databasemanager.cpp @@ -0,0 +1,117 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "databasemanager.h" +#include "luascript.h" + +extern ConfigManager g_config; + +bool DatabaseManager::optimizeTables() +{ + Database* db = Database::getInstance(); + std::ostringstream query; + + query << "SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `DATA_FREE` > 0"; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + do { + std::string tableName = result->getString("TABLE_NAME"); + std::cout << "> Optimizing table " << tableName << "..." << std::flush; + + query.str(std::string()); + query << "OPTIMIZE TABLE `" << tableName << '`'; + + if (db->executeQuery(query.str())) { + std::cout << " [success]" << std::endl; + } else { + std::cout << " [failed]" << std::endl; + } + } while (result->next()); + return true; +} + +bool DatabaseManager::tableExists(const std::string& tableName) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)) << " AND `TABLE_NAME` = " << db->escapeString(tableName) << " LIMIT 1"; + return db->storeQuery(query.str()).get() != nullptr; +} + +bool DatabaseManager::isDatabaseSetup() +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `TABLE_NAME` FROM `information_schema`.`tables` WHERE `TABLE_SCHEMA` = " << db->escapeString(g_config.getString(ConfigManager::MYSQL_DB)); + return db->storeQuery(query.str()).get() != nullptr; +} + +int32_t DatabaseManager::getDatabaseVersion() +{ + if (!tableExists("server_config")) { + Database* db = Database::getInstance(); + db->executeQuery("CREATE TABLE `server_config` (`config` VARCHAR(50) NOT NULL, `value` VARCHAR(256) NOT NULL DEFAULT '', UNIQUE(`config`)) ENGINE = InnoDB"); + db->executeQuery("INSERT INTO `server_config` VALUES ('db_version', 0)"); + return 0; + } + + int32_t version = 0; + if (getDatabaseConfig("db_version", version)) { + return version; + } + return -1; +} + +bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + value = result->getNumber("value"); + return true; +} + +void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + + int32_t tmp; + + if (!getDatabaseConfig(config, tmp)) { + query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')"; + } else { + query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config); + } + + db->executeQuery(query.str()); +} diff --git a/src/databasemanager.h b/src/databasemanager.h new file mode 100644 index 0000000..4592687 --- /dev/null +++ b/src/databasemanager.h @@ -0,0 +1,37 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C +#define FS_DATABASEMANAGER_H_2B75821C555E4D1D83E32B20D683217C +#include "database.h" + +class DatabaseManager +{ + public: + static bool tableExists(const std::string& table); + + static int32_t getDatabaseVersion(); + static bool isDatabaseSetup(); + + static bool optimizeTables(); + + static bool getDatabaseConfig(const std::string& config, int32_t& value); + static void registerDatabaseConfig(const std::string& config, int32_t value); +}; +#endif diff --git a/src/databasetasks.cpp b/src/databasetasks.cpp new file mode 100644 index 0000000..a741e9f --- /dev/null +++ b/src/databasetasks.cpp @@ -0,0 +1,101 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "databasetasks.h" +#include "tasks.h" + +extern Dispatcher g_dispatcher; + + +void DatabaseTasks::start() +{ + db.connect(); + ThreadHolder::start(); +} + +void DatabaseTasks::threadMain() +{ + std::unique_lock taskLockUnique(taskLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { + taskLockUnique.lock(); + if (tasks.empty()) { + taskSignal.wait(taskLockUnique); + } + + if (!tasks.empty()) { + DatabaseTask task = std::move(tasks.front()); + tasks.pop_front(); + taskLockUnique.unlock(); + runTask(task); + } else { + taskLockUnique.unlock(); + } + } +} + +void DatabaseTasks::addTask(const std::string& query, const std::function& callback/* = nullptr*/, bool store/* = false*/) +{ + bool signal = false; + taskLock.lock(); + if (getState() == THREAD_STATE_RUNNING) { + signal = tasks.empty(); + tasks.emplace_back(query, callback, store); + } + taskLock.unlock(); + + if (signal) { + taskSignal.notify_one(); + } +} + +void DatabaseTasks::runTask(const DatabaseTask& task) +{ + bool success; + DBResult_ptr result; + if (task.store) { + result = db.storeQuery(task.query); + success = true; + } else { + result = nullptr; + success = db.executeQuery(task.query); + } + + if (task.callback) { + g_dispatcher.addTask(createTask(std::bind(task.callback, result, success))); + } +} + +void DatabaseTasks::flush() +{ + while (!tasks.empty()) { + runTask(tasks.front()); + tasks.pop_front(); + } +} + +void DatabaseTasks::shutdown() +{ + taskLock.lock(); + setState(THREAD_STATE_TERMINATED); + flush(); + taskLock.unlock(); + taskSignal.notify_one(); +} diff --git a/src/databasetasks.h b/src/databasetasks.h new file mode 100644 index 0000000..131a173 --- /dev/null +++ b/src/databasetasks.h @@ -0,0 +1,60 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 +#define FS_DATABASETASKS_H_9CBA08E9F5FEBA7275CCEE6560059576 + +#include +#include "thread_holder_base.h" +#include "database.h" +#include "enums.h" + +struct DatabaseTask { + DatabaseTask(std::string query, std::function callback, bool store) : + query(std::move(query)), callback(std::move(callback)), store(store) {} + + std::string query; + std::function callback; + bool store; +}; + +class DatabaseTasks : public ThreadHolder +{ + public: + DatabaseTasks() = default; + void start(); + void flush(); + void shutdown(); + + void addTask(const std::string& query, const std::function& callback = nullptr, bool store = false); + + void threadMain(); + private: + void runTask(const DatabaseTask& task); + + Database db; + std::thread thread; + std::list tasks; + std::mutex taskLock; + std::condition_variable taskSignal; +}; + +extern DatabaseTasks g_databaseTasks; + +#endif diff --git a/src/definitions.h b/src/definitions.h new file mode 100644 index 0000000..344a22b --- /dev/null +++ b/src/definitions.h @@ -0,0 +1,75 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 +#define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 + +static constexpr auto STATUS_SERVER_NAME = "Nostalrius"; +static constexpr auto STATUS_SERVER_VERSION = "3.0"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "Alejandro Mujica"; + +static constexpr auto CLIENT_VERSION_MIN = 772; +static constexpr auto CLIENT_VERSION_MAX = 772; +static constexpr auto CLIENT_VERSION_STR = "7.72"; + +#ifndef __FUNCTION__ +#define __FUNCTION__ __func__ +#endif + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +#include + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#define WIN32_LEAN_AND_MEAN + +#ifdef _MSC_VER +#ifdef NDEBUG +#define _SECURE_SCL 0 +#define HAS_ITERATOR_DEBUGGING 0 +#endif + +#pragma warning(disable:4127) // conditional expression is constant +#pragma warning(disable:4244) // 'argument' : conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4250) // 'class1' : inherits 'class2::member' via dominance +#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data +#pragma warning(disable:4351) // new behavior: elements of array will be default initialized +#pragma warning(disable:4458) // declaration hides class member +#endif + +#define strcasecmp _stricmp +#define strncasecmp _strnicmp + +#ifndef _WIN32_WINNT +// 0x0602: Windows 7 +#define _WIN32_WINNT 0x0602 +#endif +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#endif diff --git a/src/depotlocker.cpp b/src/depotlocker.cpp new file mode 100644 index 0000000..10f5f15 --- /dev/null +++ b/src/depotlocker.cpp @@ -0,0 +1,89 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "depotlocker.h" +#include "creature.h" +#include "player.h" +#include "tools.h" + +DepotLocker::DepotLocker(uint16_t type) : + Container(type, 30), depotId(0) {} + +Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_DEPOT_ID) { + if (!propStream.read(depotId)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +ReturnValue DepotLocker::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + if (!skipLimit) { + int32_t addCount = 0; + + if ((item->isStackable() && item->getItemCount() != count)) { + addCount = 1; + } + + if (item->getTopParent() != this) { + if (const Container* container = item->getContainer()) { + addCount = container->getItemHoldingCount() + 1; + } else { + addCount = 1; + } + } + + if (actor) { + Player* player = actor->getPlayer(); + if (player) { + if (getItemHoldingCount() + addCount > player->getMaxDepotItems()) { + return RETURNVALUE_DEPOTISFULL; + } + } + } + } + + return Container::queryAdd(index, thing, count, flags, actor); +} + +void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + if (parent != nullptr) { + parent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + if (parent != nullptr) { + parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} diff --git a/src/depotlocker.h b/src/depotlocker.h new file mode 100644 index 0000000..9d03f8b --- /dev/null +++ b/src/depotlocker.h @@ -0,0 +1,63 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 +#define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 + +#include "container.h" + +class DepotLocker final : public Container +{ + public: + explicit DepotLocker(uint16_t type); + + DepotLocker* getDepotLocker() final { + return this; + } + const DepotLocker* getDepotLocker() const final { + return this; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + + uint16_t getDepotId() const { + return depotId; + } + void setDepotId(uint16_t depotId) { + this->depotId = depotId; + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + bool canRemove() const final { + return false; + } + + private: + uint16_t depotId; +}; + +#endif + diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 0000000..c4a89d7 --- /dev/null +++ b/src/enums.h @@ -0,0 +1,382 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE +#define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE + +enum ThreadState { + THREAD_STATE_RUNNING, + THREAD_STATE_CLOSING, + THREAD_STATE_TERMINATED, +}; + +enum itemAttrTypes : uint32_t { + ITEM_ATTRIBUTE_NONE, + + ITEM_ATTRIBUTE_ACTIONID = 1 << 0, + ITEM_ATTRIBUTE_MOVEMENTID = 1 << 1, + ITEM_ATTRIBUTE_DESCRIPTION = 1 << 2, + ITEM_ATTRIBUTE_TEXT = 1 << 3, + ITEM_ATTRIBUTE_DATE = 1 << 4, + ITEM_ATTRIBUTE_WRITER = 1 << 5, + ITEM_ATTRIBUTE_NAME = 1 << 6, + ITEM_ATTRIBUTE_ARTICLE = 1 << 7, + ITEM_ATTRIBUTE_PLURALNAME = 1 << 8, + ITEM_ATTRIBUTE_WEIGHT = 1 << 9, + ITEM_ATTRIBUTE_ATTACK = 1 << 10, + ITEM_ATTRIBUTE_DEFENSE = 1 << 11, + ITEM_ATTRIBUTE_ARMOR = 1 << 12, + ITEM_ATTRIBUTE_SHOOTRANGE = 1 << 13, + ITEM_ATTRIBUTE_OWNER = 1 << 14, + ITEM_ATTRIBUTE_DURATION = 1 << 15, + ITEM_ATTRIBUTE_DECAYSTATE = 1 << 16, + ITEM_ATTRIBUTE_CORPSEOWNER = 1 << 17, + ITEM_ATTRIBUTE_CHARGES = 1 << 18, + ITEM_ATTRIBUTE_FLUIDTYPE = 1 << 19, + ITEM_ATTRIBUTE_DOORID = 1 << 20, + ITEM_ATTRIBUTE_KEYNUMBER = 1 << 21, + ITEM_ATTRIBUTE_KEYHOLENUMBER = 1 << 22, + ITEM_ATTRIBUTE_DOORQUESTNUMBER = 1 << 23, + ITEM_ATTRIBUTE_DOORQUESTVALUE = 1 << 24, + ITEM_ATTRIBUTE_DOORLEVEL = 1 << 25, + ITEM_ATTRIBUTE_CHESTQUESTNUMBER = 1 << 26, +}; + +enum VipStatus_t : uint8_t { + VIPSTATUS_OFFLINE = 0, + VIPSTATUS_ONLINE = 1, +}; + +enum OperatingSystem_t : uint8_t { + CLIENTOS_NONE = 0, + + CLIENTOS_LINUX = 1, + CLIENTOS_WINDOWS = 2, + CLIENTOS_FLASH = 3, + + CLIENTOS_OTCLIENT_LINUX = 10, + CLIENTOS_OTCLIENT_WINDOWS = 11, + CLIENTOS_OTCLIENT_MAC = 12, +}; + +enum AccountType_t : uint8_t { + ACCOUNT_TYPE_NORMAL = 1, + ACCOUNT_TYPE_TUTOR = 2, + ACCOUNT_TYPE_SENIORTUTOR = 3, + ACCOUNT_TYPE_GAMEMASTER = 4, + ACCOUNT_TYPE_GOD = 5 +}; + +enum RaceType_t : uint8_t { + RACE_NONE, + RACE_VENOM, + RACE_BLOOD, + RACE_UNDEAD, + RACE_FIRE, +}; + +enum CombatType_t : uint16_t { + COMBAT_NONE = 0, + + COMBAT_PHYSICALDAMAGE = 1 << 0, + COMBAT_ENERGYDAMAGE = 1 << 1, + COMBAT_EARTHDAMAGE = 1 << 2, + COMBAT_FIREDAMAGE = 1 << 3, + COMBAT_UNDEFINEDDAMAGE = 1 << 4, + COMBAT_LIFEDRAIN = 1 << 5, + COMBAT_MANADRAIN = 1 << 6, + COMBAT_HEALING = 1 << 7, + + COMBAT_COUNT = 9 +}; + +enum CombatParam_t { + COMBAT_PARAM_TYPE, + COMBAT_PARAM_EFFECT, + COMBAT_PARAM_DISTANCEEFFECT, + COMBAT_PARAM_BLOCKSHIELD, + COMBAT_PARAM_BLOCKARMOR, + COMBAT_PARAM_TARGETCASTERORTOPMOST, + COMBAT_PARAM_CREATEITEM, + COMBAT_PARAM_AGGRESSIVE, + COMBAT_PARAM_DISPEL, + COMBAT_PARAM_USECHARGES, + COMBAT_PARAM_DECREASEDAMAGE, + COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE, +}; + +enum fightMode_t : uint8_t { + FIGHTMODE_ATTACK = 1, + FIGHTMODE_BALANCED = 2, + FIGHTMODE_DEFENSE = 3, +}; + +enum CallBackParam_t { + CALLBACK_PARAM_LEVELMAGICVALUE, + CALLBACK_PARAM_SKILLVALUE, + CALLBACK_PARAM_TARGETTILE, + CALLBACK_PARAM_TARGETCREATURE, +}; + +enum ConditionParam_t { + CONDITION_PARAM_OWNER = 1, + CONDITION_PARAM_TICKS = 2, + //CONDITION_PARAM_OUTFIT = 3, + CONDITION_PARAM_HEALTHGAIN = 4, + CONDITION_PARAM_HEALTHTICKS = 5, + CONDITION_PARAM_MANAGAIN = 6, + CONDITION_PARAM_MANATICKS = 7, + CONDITION_PARAM_DELAYED = 8, + CONDITION_PARAM_SPEED = 9, + CONDITION_PARAM_LIGHT_LEVEL = 10, + CONDITION_PARAM_LIGHT_COLOR = 11, + CONDITION_PARAM_SOULGAIN = 12, + CONDITION_PARAM_SOULTICKS = 13, + CONDITION_PARAM_MINVALUE = 14, + CONDITION_PARAM_MAXVALUE = 15, + CONDITION_PARAM_STARTVALUE = 16, + CONDITION_PARAM_TICKINTERVAL = 17, + CONDITION_PARAM_SKILL_MELEE = 19, + CONDITION_PARAM_SKILL_FIST = 20, + CONDITION_PARAM_SKILL_CLUB = 21, + CONDITION_PARAM_SKILL_SWORD = 22, + CONDITION_PARAM_SKILL_AXE = 23, + CONDITION_PARAM_SKILL_DISTANCE = 24, + CONDITION_PARAM_SKILL_SHIELD = 25, + CONDITION_PARAM_SKILL_FISHING = 26, + CONDITION_PARAM_STAT_MAXHITPOINTS = 27, + CONDITION_PARAM_STAT_MAXMANAPOINTS = 28, + // CONDITION_PARAM_STAT_SOULPOINTS = 29, + CONDITION_PARAM_STAT_MAGICPOINTS = 30, + CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT = 31, + CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT = 32, + // CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33, + CONDITION_PARAM_STAT_MAGICPOINTSPERCENT = 34, + CONDITION_PARAM_PERIODICDAMAGE = 35, + CONDITION_PARAM_SKILL_MELEEPERCENT = 36, + CONDITION_PARAM_SKILL_FISTPERCENT = 37, + CONDITION_PARAM_SKILL_CLUBPERCENT = 38, + CONDITION_PARAM_SKILL_SWORDPERCENT = 39, + CONDITION_PARAM_SKILL_AXEPERCENT = 40, + CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41, + CONDITION_PARAM_SKILL_SHIELDPERCENT = 42, + CONDITION_PARAM_SKILL_FISHINGPERCENT = 43, + // CONDITION_PARAM_BUFF_SPELL = 44, + CONDITION_PARAM_SUBID = 45, + CONDITION_PARAM_FIELD = 46, + CONDITION_PARAM_CYCLE = 47, + CONDITION_PARAM_HIT_DAMAGE = 48, + CONDITION_PARAM_COUNT = 49, + CONDITION_PARAM_MAX_COUNT = 50, +}; + +enum BlockType_t : uint8_t { + BLOCK_NONE, + BLOCK_DEFENSE, + BLOCK_ARMOR, + BLOCK_IMMUNITY +}; + +enum skills_t : uint8_t { + SKILL_FIST = 0, + SKILL_CLUB = 1, + SKILL_SWORD = 2, + SKILL_AXE = 3, + SKILL_DISTANCE = 4, + SKILL_SHIELD = 5, + SKILL_FISHING = 6, + + SKILL_MAGLEVEL = 7, + SKILL_LEVEL = 8, + + SKILL_FIRST = SKILL_FIST, + SKILL_LAST = SKILL_FISHING +}; + +enum stats_t { + STAT_MAXHITPOINTS, + STAT_MAXMANAPOINTS, + STAT_SOULPOINTS, // unused + STAT_MAGICPOINTS, + + STAT_FIRST = STAT_MAXHITPOINTS, + STAT_LAST = STAT_MAGICPOINTS +}; + +enum formulaType_t { + COMBAT_FORMULA_UNDEFINED, + COMBAT_FORMULA_LEVELMAGIC, + COMBAT_FORMULA_SKILL, + COMBAT_FORMULA_DAMAGE, +}; + +enum ConditionType_t { + CONDITION_NONE, + + CONDITION_POISON = 1 << 0, + CONDITION_FIRE = 1 << 1, + CONDITION_ENERGY = 1 << 2, + CONDITION_HASTE = 1 << 3, + CONDITION_PARALYZE = 1 << 4, + CONDITION_OUTFIT = 1 << 5, + CONDITION_INVISIBLE = 1 << 6, + CONDITION_LIGHT = 1 << 7, + CONDITION_MANASHIELD = 1 << 8, + CONDITION_INFIGHT = 1 << 9, + CONDITION_DRUNK = 1 << 10, + CONDITION_REGENERATION = 1 << 11, + CONDITION_SOUL = 1 << 12, + CONDITION_MUTED = 1 << 13, + CONDITION_CHANNELMUTEDTICKS = 1 << 14, + CONDITION_YELLTICKS = 1 << 15, + CONDITION_ATTRIBUTES = 1 << 16, + CONDITION_EXHAUST = 1 << 17, + CONDITION_PACIFIED = 1 << 18, + CONDITION_AGGRESSIVE = 1 << 19, +}; + +enum ConditionId_t : int8_t { + CONDITIONID_DEFAULT = -1, + CONDITIONID_COMBAT, + CONDITIONID_HEAD, + CONDITIONID_NECKLACE, + CONDITIONID_BACKPACK, + CONDITIONID_ARMOR, + CONDITIONID_RIGHT, + CONDITIONID_LEFT, + CONDITIONID_LEGS, + CONDITIONID_FEET, + CONDITIONID_RING, + CONDITIONID_AMMO, +}; + +enum PlayerSex_t : uint8_t { + PLAYERSEX_FEMALE = 0, + PLAYERSEX_MALE = 1, + + PLAYERSEX_LAST = PLAYERSEX_MALE +}; + +enum Vocation_t : uint16_t { + VOCATION_NONE, + VOCATION_SORCERER = 1 << 0, + VOCATION_DRUID = 1 << 1, + VOCATION_PALADIN = 1 << 2, + VOCATION_KNIGHT = 1 << 3, +}; + +enum ReturnValue { + RETURNVALUE_NOERROR, + RETURNVALUE_NOTPOSSIBLE, + RETURNVALUE_NOTENOUGHROOM, + RETURNVALUE_PLAYERISPZLOCKED, + RETURNVALUE_PLAYERISNOTINVITED, + RETURNVALUE_CANNOTTHROW, + RETURNVALUE_THEREISNOWAY, + RETURNVALUE_DESTINATIONOUTOFREACH, + RETURNVALUE_CREATUREBLOCK, + RETURNVALUE_NOTMOVEABLE, + RETURNVALUE_DROPTWOHANDEDITEM, + RETURNVALUE_BOTHHANDSNEEDTOBEFREE, + RETURNVALUE_CANONLYUSEONEWEAPON, + RETURNVALUE_NEEDEXCHANGE, + RETURNVALUE_CANNOTBEDRESSED, + RETURNVALUE_PUTTHISOBJECTINYOURHAND, + RETURNVALUE_PUTTHISOBJECTINBOTHHANDS, + RETURNVALUE_TOOFARAWAY, + RETURNVALUE_FIRSTGODOWNSTAIRS, + RETURNVALUE_FIRSTGOUPSTAIRS, + RETURNVALUE_CONTAINERNOTENOUGHROOM, + RETURNVALUE_NOTENOUGHCAPACITY, + RETURNVALUE_CANNOTPICKUP, + RETURNVALUE_THISISIMPOSSIBLE, + RETURNVALUE_DEPOTISFULL, + RETURNVALUE_CREATUREDOESNOTEXIST, + RETURNVALUE_CANNOTUSETHISOBJECT, + RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE, + RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE, + RETURNVALUE_YOUAREALREADYTRADING, + RETURNVALUE_THISPLAYERISALREADYTRADING, + RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT, + RETURNVALUE_DIRECTPLAYERSHOOT, + RETURNVALUE_NOTENOUGHLEVEL, + RETURNVALUE_NOTENOUGHMAGICLEVEL, + RETURNVALUE_NOTENOUGHMANA, + RETURNVALUE_NOTENOUGHSOUL, + RETURNVALUE_YOUAREEXHAUSTED, + RETURNVALUE_PLAYERISNOTREACHABLE, + RETURNVALUE_CANONLYUSETHISRUNEONCREATURES, + RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER, + RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE, + RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE, + RETURNVALUE_YOUCANONLYUSEITONCREATURES, + RETURNVALUE_CREATUREISNOTREACHABLE, + RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS, + RETURNVALUE_YOUNEEDPREMIUMACCOUNT, + RETURNVALUE_YOUNEEDTOLEARNTHISSPELL, + RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL, + RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL, + RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE, + RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE, + RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE, + RETURNVALUE_YOUCANNOTLOGOUTHERE, + RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL, + RETURNVALUE_CANNOTCONJUREITEMHERE, + RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS, + RETURNVALUE_NAMEISTOOAMBIGIOUS, + RETURNVALUE_CANONLYUSEONESHIELD, + RETURNVALUE_NOPARTYMEMBERSINRANGE, + RETURNVALUE_YOUARENOTTHEOWNER, +}; + +struct Outfit_t { + uint16_t lookType = 0; + uint16_t lookTypeEx = 0; + uint8_t lookHead = 0; + uint8_t lookBody = 0; + uint8_t lookLegs = 0; + uint8_t lookFeet = 0; +}; + +struct LightInfo { + uint8_t level = 0; + uint8_t color = 0; + constexpr LightInfo() = default; + constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {} +}; + +struct CombatDamage +{ + CombatType_t type; + int32_t value; + int32_t min; + int32_t max; + + CombatDamage() + { + type = COMBAT_NONE; + value = 0; + min = 0; + max = 0; + } +}; + +#endif diff --git a/src/fileloader.cpp b/src/fileloader.cpp new file mode 100644 index 0000000..d37955f --- /dev/null +++ b/src/fileloader.cpp @@ -0,0 +1,405 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "fileloader.h" + +FileLoader::~FileLoader() +{ + if (file) { + fclose(file); + file = nullptr; + } + + NodeStruct::clearNet(root); + delete[] buffer; + + for (auto& i : cached_data) { + delete[] i.data; + } +} + +bool FileLoader::openFile(const char* filename, const char* accept_identifier) +{ + file = fopen(filename, "rb"); + if (!file) { + lastError = ERROR_CAN_NOT_OPEN; + return false; + } + + char identifier[4]; + if (fread(identifier, 1, 4, file) < 4) { + fclose(file); + file = nullptr; + lastError = ERROR_EOF; + return false; + } + + // The first four bytes must either match the accept identifier or be 0x00000000 (wildcard) + if (memcmp(identifier, accept_identifier, 4) != 0 && memcmp(identifier, "\0\0\0\0", 4) != 0) { + fclose(file); + file = nullptr; + lastError = ERROR_INVALID_FILE_VERSION; + return false; + } + + fseek(file, 0, SEEK_END); + int32_t file_size = ftell(file); + cache_size = std::min(32768, std::max(file_size / 20, 8192)) & ~0x1FFF; + + if (!safeSeek(4)) { + lastError = ERROR_INVALID_FORMAT; + return false; + } + + delete root; + root = new NodeStruct(); + root->start = 4; + + int32_t byte; + if (safeSeek(4) && readByte(byte) && byte == NODE_START) { + return parseNode(root); + } + + return false; +} + +bool FileLoader::parseNode(NODE node) +{ + int32_t byte, pos; + NODE currentNode = node; + + while (readByte(byte)) { + currentNode->type = byte; + bool setPropsSize = false; + + while (true) { + if (!readByte(byte)) { + return false; + } + + bool skipNode = false; + + switch (byte) { + case NODE_START: { + //child node start + if (!safeTell(pos)) { + return false; + } + + NODE childNode = new NodeStruct(); + childNode->start = pos; + currentNode->propsSize = pos - currentNode->start - 2; + currentNode->child = childNode; + + setPropsSize = true; + + if (!parseNode(childNode)) { + return false; + } + + break; + } + + case NODE_END: { + //current node end + if (!setPropsSize) { + if (!safeTell(pos)) { + return false; + } + + currentNode->propsSize = pos - currentNode->start - 2; + } + + if (!readByte(byte)) { + return true; + } + + switch (byte) { + case NODE_START: { + //starts next node + if (!safeTell(pos)) { + return false; + } + + skipNode = true; + NODE nextNode = new NodeStruct(); + nextNode->start = pos; + currentNode->next = nextNode; + currentNode = nextNode; + break; + } + + case NODE_END: + return safeTell(pos) && safeSeek(pos); + + default: + lastError = ERROR_INVALID_FORMAT; + return false; + } + + break; + } + + case ESCAPE_CHAR: { + if (!readByte(byte)) { + return false; + } + + break; + } + + default: + break; + } + + if (skipNode) { + break; + } + } + } + return false; +} + +const uint8_t* FileLoader::getProps(const NODE node, size_t& size) +{ + if (!node) { + return nullptr; + } + + if (node->propsSize >= buffer_size) { + delete[] buffer; + + while (node->propsSize >= buffer_size) { + buffer_size *= 2; + } + + buffer = new uint8_t[buffer_size]; + } + + //get buffer + if (!readBytes(node->propsSize, node->start + 2)) { + return nullptr; + } + + //unscape buffer + size_t j = 0; + bool escaped = false; + for (uint32_t i = 0; i < node->propsSize; ++i, ++j) { + if (buffer[i] == ESCAPE_CHAR) { + //escape char found, skip it and write next + buffer[j] = buffer[++i]; + //is neede a displacement for next bytes + escaped = true; + } else if (escaped) { + //perform that displacement + buffer[j] = buffer[i]; + } + } + + size = j; + return buffer; +} + +bool FileLoader::getProps(const NODE node, PropStream& props) +{ + size_t size; + if (const uint8_t* a = getProps(node, size)) { + props.init(reinterpret_cast(a), size); // does not break strict aliasing + return true; + } + + props.init(nullptr, 0); + return false; +} + +NODE FileLoader::getChildNode(const NODE parent, uint32_t& type) +{ + if (parent) { + NODE child = parent->child; + if (child) { + type = child->type; + } + + return child; + } + + type = root->type; + return root; +} + +NODE FileLoader::getNextNode(const NODE prev, uint32_t& type) +{ + if (!prev) { + return NO_NODE; + } + + NODE next = prev->next; + if (next) { + type = next->type; + } + return next; +} + +inline bool FileLoader::readByte(int32_t& value) +{ + if (cache_index == NO_VALID_CACHE) { + lastError = ERROR_CACHE_ERROR; + return false; + } + + if (cache_offset >= cached_data[cache_index].size) { + int32_t pos = cache_offset + cached_data[cache_index].base; + int32_t tmp = getCacheBlock(pos); + if (tmp < 0) { + return false; + } + + cache_index = tmp; + cache_offset = pos - cached_data[cache_index].base; + if (cache_offset >= cached_data[cache_index].size) { + return false; + } + } + + value = cached_data[cache_index].data[cache_offset++]; + return true; +} + +inline bool FileLoader::readBytes(uint32_t size, int32_t pos) +{ + //seek at pos + uint32_t remain = size; + uint8_t* buf = this->buffer; + do { + //prepare cache + uint32_t i = getCacheBlock(pos); + if (i == NO_VALID_CACHE) { + return false; + } + + cache_index = i; + cache_offset = pos - cached_data[i].base; + + //get maximum read block size and calculate remaining bytes + uint32_t reading = std::min(remain, cached_data[i].size - cache_offset); + remain -= reading; + + //read it + memcpy(buf, cached_data[cache_index].data + cache_offset, reading); + + //update variables + cache_offset += reading; + buf += reading; + pos += reading; + } while (remain > 0); + return true; +} + +inline bool FileLoader::safeSeek(uint32_t pos) +{ + uint32_t i = getCacheBlock(pos); + if (i == NO_VALID_CACHE) { + return false; + } + + cache_index = i; + cache_offset = pos - cached_data[i].base; + return true; +} + +inline bool FileLoader::safeTell(int32_t& pos) +{ + if (cache_index == NO_VALID_CACHE) { + lastError = ERROR_CACHE_ERROR; + return false; + } + + pos = cached_data[cache_index].base + cache_offset - 1; + return true; +} + +inline uint32_t FileLoader::getCacheBlock(uint32_t pos) +{ + bool found = false; + uint32_t i, base_pos = pos & ~(cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (cached_data[i].loaded) { + if (cached_data[i].base == base_pos) { + found = true; + break; + } + } + } + + if (!found) { + i = loadCacheBlock(pos); + } + + return i; +} + +int32_t FileLoader::loadCacheBlock(uint32_t pos) +{ + int32_t i, loading_cache = -1, base_pos = pos & ~(cache_size - 1); + + for (i = 0; i < CACHE_BLOCKS; i++) { + if (!cached_data[i].loaded) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + for (i = 0; i < CACHE_BLOCKS; i++) { + if (std::abs(static_cast(cached_data[i].base) - base_pos) > static_cast(2 * cache_size)) { + loading_cache = i; + break; + } + } + + if (loading_cache == -1) { + loading_cache = 0; + } + } + + if (cached_data[loading_cache].data == nullptr) { + cached_data[loading_cache].data = new uint8_t[cache_size]; + } + + cached_data[loading_cache].base = base_pos; + + if (fseek(file, cached_data[loading_cache].base, SEEK_SET) != 0) { + lastError = ERROR_SEEK_ERROR; + return -1; + } + + uint32_t size = fread(cached_data[loading_cache].data, 1, cache_size, file); + cached_data[loading_cache].size = size; + + if (size < (pos - cached_data[loading_cache].base)) { + lastError = ERROR_SEEK_ERROR; + return -1; + } + + cached_data[loading_cache].loaded = 1; + return loading_cache; +} diff --git a/src/fileloader.h b/src/fileloader.h new file mode 100644 index 0000000..5ce9c5b --- /dev/null +++ b/src/fileloader.h @@ -0,0 +1,247 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E +#define FS_FILELOADER_H_9B663D19E58D42E6BFACFE5B09D7A05E + +#include +#include + +struct NodeStruct; + +typedef NodeStruct* NODE; + +struct NodeStruct { + uint32_t start = 0; + uint32_t propsSize = 0; + uint32_t type = 0; + NodeStruct* next = nullptr; + NodeStruct* child = nullptr; + + static void clearNet(NodeStruct* root) { + if (root) { + clearChild(root); + } + } + + private: + static void clearNext(NodeStruct* node) { + NodeStruct* deleteNode = node; + NodeStruct* nextNode; + + while (deleteNode) { + if (deleteNode->child) { + clearChild(deleteNode->child); + } + + nextNode = deleteNode->next; + delete deleteNode; + deleteNode = nextNode; + } + } + + static void clearChild(NodeStruct* node) { + if (node->child) { + clearChild(node->child); + } + + if (node->next) { + clearNext(node->next); + } + + delete node; + } +}; + +static constexpr auto NO_NODE = nullptr; + +enum FILELOADER_ERRORS { + ERROR_NONE, + ERROR_INVALID_FILE_VERSION, + ERROR_CAN_NOT_OPEN, + ERROR_CAN_NOT_CREATE, + ERROR_EOF, + ERROR_SEEK_ERROR, + ERROR_NOT_OPEN, + ERROR_INVALID_NODE, + ERROR_INVALID_FORMAT, + ERROR_TELL_ERROR, + ERROR_COULDNOTWRITE, + ERROR_CACHE_ERROR, +}; + +class PropStream; + +class FileLoader +{ + public: + FileLoader() = default; + ~FileLoader(); + + // non-copyable + FileLoader(const FileLoader&) = delete; + FileLoader& operator=(const FileLoader&) = delete; + + bool openFile(const char* filename, const char* identifier); + const uint8_t* getProps(const NODE, size_t& size); + bool getProps(const NODE, PropStream& props); + NODE getChildNode(const NODE parent, uint32_t& type); + NODE getNextNode(const NODE prev, uint32_t& type); + + FILELOADER_ERRORS getError() const { + return lastError; + } + + protected: + enum SPECIAL_BYTES { + ESCAPE_CHAR = 0xFD, + NODE_START = 0xFE, + NODE_END = 0xFF, + }; + + bool parseNode(NODE node); + + inline bool readByte(int32_t& value); + inline bool readBytes(uint32_t size, int32_t pos); + inline bool safeSeek(uint32_t pos); + inline bool safeTell(int32_t& pos); + + protected: + struct cache { + uint8_t* data; + uint32_t loaded; + uint32_t base; + uint32_t size; + }; + + static constexpr int32_t CACHE_BLOCKS = 3; + cache cached_data[CACHE_BLOCKS] = {}; + + uint8_t* buffer = new uint8_t[1024]; + NODE root = nullptr; + FILE* file = nullptr; + + FILELOADER_ERRORS lastError = ERROR_NONE; + uint32_t buffer_size = 1024; + + uint32_t cache_size = 0; + static constexpr uint32_t NO_VALID_CACHE = std::numeric_limits::max(); + uint32_t cache_index = NO_VALID_CACHE; + uint32_t cache_offset = NO_VALID_CACHE; + + inline uint32_t getCacheBlock(uint32_t pos); + int32_t loadCacheBlock(uint32_t pos); +}; + +class PropStream +{ + public: + void init(const char* a, size_t size) { + p = a; + end = a + size; + } + + size_t size() const { + return end - p; + } + + template + bool read(T& ret) { + if (size() < sizeof(T)) { + return false; + } + + memcpy(&ret, p, sizeof(T)); + p += sizeof(T); + return true; + } + + bool readString(std::string& ret) { + uint16_t strLen; + if (!read(strLen)) { + return false; + } + + if (size() < strLen) { + return false; + } + + char* str = new char[strLen + 1]; + memcpy(str, p, strLen); + str[strLen] = 0; + ret.assign(str, strLen); + delete[] str; + p += strLen; + return true; + } + + bool skip(size_t n) { + if (size() < n) { + return false; + } + + p += n; + return true; + } + + protected: + const char* p = nullptr; + const char* end = nullptr; +}; + +class PropWriteStream +{ + public: + PropWriteStream() = default; + + // non-copyable + PropWriteStream(const PropWriteStream&) = delete; + PropWriteStream& operator=(const PropWriteStream&) = delete; + + const char* getStream(size_t& size) const { + size = buffer.size(); + return buffer.data(); + } + + void clear() { + buffer.clear(); + } + + template + void write(T add) { + char* addr = reinterpret_cast(&add); + std::copy(addr, addr + sizeof(T), std::back_inserter(buffer)); + } + + void writeString(const std::string& str) { + size_t strLength = str.size(); + if (strLength > std::numeric_limits::max()) { + write(0); + return; + } + + write(static_cast(strLength)); + std::copy(str.begin(), str.end(), std::back_inserter(buffer)); + } + + protected: + std::vector buffer; +}; + +#endif diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..1f41476 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,4536 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "pugicast.h" + +#include "items.h" +#include "commands.h" +#include "creature.h" +#include "monster.h" +#include "game.h" +#include "actions.h" +#include "iologindata.h" +#include "talkaction.h" +#include "spells.h" +#include "configmanager.h" +#include "server.h" +#include "globalevent.h" +#include "bed.h" +#include "scheduler.h" +#include "databasetasks.h" + +extern ConfigManager g_config; +extern Actions* g_actions; +extern Chat* g_chat; +extern TalkActions* g_talkActions; +extern Spells* g_spells; +extern Vocations g_vocations; +extern GlobalEvents* g_globalEvents; + +Game::~Game() +{ + for (const auto& it : guilds) { + delete it.second; + } +} + +void Game::start(ServiceManager* manager) +{ + serviceManager = manager; + + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + g_scheduler.addEvent(createSchedulerTask(EVENT_CREATURE_THINK_INTERVAL, std::bind(&Game::checkCreatures, this, 0))); + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); +} + +GameState_t Game::getGameState() const +{ + return gameState; +} + +void Game::setWorldType(WorldType_t type) +{ + worldType = type; +} + +void Game::setGameState(GameState_t newState) +{ + if (gameState == GAME_STATE_SHUTDOWN) { + return; //this cannot be stopped + } + + if (gameState == newState) { + return; + } + + gameState = newState; + switch (newState) { + case GAME_STATE_INIT: { + commands.loadFromXml(); + + loadExperienceStages(); + + groups.load(); + g_chat->load(); + + map.spawns.startup(); + + raids.loadFromXml(); + raids.startup(); + + loadMotdNum(); + loadPlayersRecord(); + + g_globalEvents->startup(); + break; + } + + case GAME_STATE_SHUTDOWN: { + g_globalEvents->execute(GLOBALEVENT_SHUTDOWN); + + //kick all players that are still online + auto it = players.begin(); + while (it != players.end()) { + it->second->kickPlayer(true); + it = players.begin(); + } + + saveMotdNum(); + saveGameState(); + + g_dispatcher.addTask( + createTask(std::bind(&Game::shutdown, this))); + + g_scheduler.stop(); + g_databaseTasks.stop(); + g_dispatcher.stop(); + break; + } + + case GAME_STATE_CLOSED: { + /* kick all players without the CanAlwaysLogin flag */ + auto it = players.begin(); + while (it != players.end()) { + if (!it->second->hasFlag(PlayerFlag_CanAlwaysLogin)) { + it->second->kickPlayer(true); + it = players.begin(); + } else { + ++it; + } + } + + saveGameState(); + break; + } + + default: + break; + } +} + +void Game::saveGameState() +{ + if (gameState == GAME_STATE_NORMAL) { + setGameState(GAME_STATE_MAINTAIN); + } + + std::cout << "Saving server..." << std::endl; + + for (const auto& it : players) { + it.second->loginPosition = it.second->getPosition(); + IOLoginData::savePlayer(it.second); + } + + Map::save(); + + if (gameState == GAME_STATE_MAINTAIN) { + setGameState(GAME_STATE_NORMAL); + } +} + +bool Game::loadMainMap(const std::string& filename) +{ + Monster::despawnRange = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); + Monster::despawnRadius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); + return map.loadMap("data/world/" + filename + ".otbm", true); +} + +void Game::loadMap(const std::string& path) +{ + map.loadMap(path, false); +} + +Cylinder* Game::internalGetCylinder(Player* player, const Position& pos) const +{ + if (pos.x != 0xFFFF) { + return map.getTile(pos); + } + + //container + if (pos.y & 0x40) { + uint8_t from_cid = pos.y & 0x0F; + return player->getContainerByID(from_cid); + } + + //inventory + return player; +} + +Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index, uint32_t spriteId, stackPosType_t type) const +{ + if (pos.x != 0xFFFF) { + Tile* tile = map.getTile(pos); + if (!tile) { + return nullptr; + } + + Thing* thing; + switch (type) { + case STACKPOS_LOOK: { + return tile->getTopVisibleThing(player); + } + + case STACKPOS_MOVE: { + Item* item = tile->getTopDownItem(); + if (item && item->isMoveable()) { + thing = item; + } else { + thing = tile->getTopVisibleCreature(player); + } + break; + } + + case STACKPOS_USEITEM: { + thing = tile->getUseItem(); + break; + } + + case STACKPOS_TOPDOWN_ITEM: { + thing = tile->getTopDownItem(); + break; + } + + case STACKPOS_USETARGET: { + thing = tile->getTopCreature(); + if (!thing) { + thing = tile->getUseItem(); + } + break; + } + + default: { + thing = nullptr; + break; + } + } + + if (player && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + //do extra checks here if the thing is accessable + if (thing && thing->getItem()) { + if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { + if (player->getPosition().x + 1 == tile->getPosition().x) { + thing = nullptr; + } + } else { // horizontal + if (player->getPosition().y + 1 == tile->getPosition().y) { + thing = nullptr; + } + } + } + } + return thing; + } + + //container + if (pos.y & 0x40) { + uint8_t fromCid = pos.y & 0x0F; + + Container* parentContainer = player->getContainerByID(fromCid); + if (!parentContainer) { + return nullptr; + } + + uint8_t slot = pos.z; + return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot); + } else if (pos.y == 0 && pos.z == 0) { + const ItemType& it = Item::items.getItemType(spriteId); + if (it.id == 0) { + return nullptr; + } + + int32_t subType; + if (it.isFluidContainer()) { + subType = static_cast(index); + } else { + subType = -1; + } + + return findItemOfType(player, it.id, true, subType); + } + + //inventory + slots_t slot = static_cast(pos.y); + return player->getInventoryItem(slot); +} + +void Game::internalGetPosition(Item* item, Position& pos, uint8_t& stackpos) +{ + pos.x = 0; + pos.y = 0; + pos.z = 0; + stackpos = 0; + + Cylinder* topParent = item->getTopParent(); + if (topParent) { + if (Player* player = dynamic_cast(topParent)) { + pos.x = 0xFFFF; + + Container* container = dynamic_cast(item->getParent()); + if (container) { + pos.y = static_cast(0x40) | static_cast(player->getContainerID(container)); + pos.z = container->getThingIndex(item); + stackpos = pos.z; + } else { + pos.y = player->getThingIndex(item); + stackpos = pos.y; + } + } else if (Tile* tile = topParent->getTile()) { + pos = tile->getPosition(); + stackpos = tile->getThingIndex(item); + } + } +} + +Creature* Game::getCreatureByID(uint32_t id) +{ + if (id <= Player::playerAutoID) { + return getPlayerByID(id); + } else if (id <= Monster::monsterAutoID) { + return getMonsterByID(id); + } else if (id <= Npc::npcAutoID) { + return getNpcByID(id); + } + return nullptr; +} + +Monster* Game::getMonsterByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = monsters.find(id); + if (it == monsters.end()) { + return nullptr; + } + return it->second; +} + +Npc* Game::getNpcByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = npcs.find(id); + if (it == npcs.end()) { + return nullptr; + } + return it->second; +} + +Player* Game::getPlayerByID(uint32_t id) +{ + if (id == 0) { + return nullptr; + } + + auto it = players.find(id); + if (it == players.end()) { + return nullptr; + } + return it->second; +} + +Creature* Game::getCreatureByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + const std::string& lowerCaseName = asLowerCaseString(s); + + auto m_it = mappedPlayerNames.find(lowerCaseName); + if (m_it != mappedPlayerNames.end()) { + return m_it->second; + } + + for (const auto& it : npcs) { + if (lowerCaseName == asLowerCaseString(it.second->getName())) { + return it.second; + } + } + + for (const auto& it : monsters) { + if (lowerCaseName == asLowerCaseString(it.second->getName())) { + return it.second; + } + } + return nullptr; +} + +Npc* Game::getNpcByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + const char* npcName = s.c_str(); + for (const auto& it : npcs) { + if (strcasecmp(npcName, it.second->getName().c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +Player* Game::getPlayerByName(const std::string& s) +{ + if (s.empty()) { + return nullptr; + } + + auto it = mappedPlayerNames.find(asLowerCaseString(s)); + if (it == mappedPlayerNames.end()) { + return nullptr; + } + return it->second; +} + +Player* Game::getPlayerByGUID(const uint32_t& guid) +{ + if (guid == 0) { + return nullptr; + } + + for (const auto& it : players) { + if (guid == it.second->getGUID()) { + return it.second; + } + } + return nullptr; +} + +ReturnValue Game::getPlayerByNameWildcard(const std::string& s, Player*& player) +{ + size_t strlen = s.length(); + if (strlen == 0 || strlen > 20) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + + if (s.back() == '~') { + const std::string& query = asLowerCaseString(s.substr(0, strlen - 1)); + std::string result; + ReturnValue ret = wildcardTree.findOne(query, result); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + player = getPlayerByName(result); + } else { + player = getPlayerByName(s); + } + + if (!player) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + + return RETURNVALUE_NOERROR; +} + +Player* Game::getPlayerByAccount(uint32_t acc) +{ + for (const auto& it : players) { + if (it.second->getAccount() == acc) { + return it.second; + } + } + return nullptr; +} + +bool Game::internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (creature->getParent() != nullptr) { + return false; + } + + if (!map.placeCreature(pos, creature, extendedPos, forced)) { + return false; + } + + creature->incrementReferenceCounter(); + creature->setID(); + creature->addList(); + return true; +} + +bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedPos /*=false*/, bool forced /*= false*/) +{ + if (!internalPlaceCreature(creature, pos, extendedPos, forced)) { + return false; + } + + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true); + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); + } + } + + for (Creature* spectator : list) { + spectator->onCreatureAppear(creature, true); + } + + creature->getParent()->postAddNotification(creature, nullptr, 0); + + addCreatureCheck(creature); + creature->onPlacedCreature(); + return true; +} + +bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) +{ + if (creature->isRemoved()) { + return false; + } + + Tile* tile = creature->getTile(); + + std::vector oldStackPosVector; + + SpectatorVec list; + map.getSpectators(list, tile->getPosition(), true); + for (Creature* spectator : list) { + if (Player* player = spectator->getPlayer()) { + oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); + } + } + + tile->removeCreature(creature); + + const Position& tilePosition = tile->getPosition(); + + //send to client + size_t i = 0; + for (Creature* spectator : list) { + if (Player* player = spectator->getPlayer()) { + player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); + } + } + + //event method + for (Creature* spectator : list) { + spectator->onRemoveCreature(creature, isLogout); + } + + creature->getParent()->postRemoveNotification(creature, nullptr, 0); + + creature->removeList(); + creature->setRemoved(); + ReleaseCreature(creature); + + removeCreatureCheck(creature); + + for (Creature* summon : creature->summons) { + summon->setLossSkill(false); + removeCreature(summon); + } + return true; +} + +void Game::playerMoveThing(uint32_t playerId, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint8_t fromIndex = 0; + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = fromPos.z; + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (Creature* movingCreature = thing->getCreature()) { + Tile* tile = map.getTile(toPos); + if (!tile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (Position::areInRange<1, 1, 0>(movingCreature->getPosition(), player->getPosition())) { + SchedulerTask* task = createSchedulerTask(1000, + std::bind(&Game::playerMoveCreatureByID, this, player->getID(), + movingCreature->getID(), movingCreature->getPosition(), tile->getPosition())); + player->setNextActionTask(task); + } else { + playerMoveCreature(player, movingCreature, movingCreature->getPosition(), tile); + } + } else if (thing->getItem()) { + Cylinder* toCylinder = internalGetCylinder(player, toPos); + if (!toCylinder) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, thing->getItem(), toCylinder); + } +} + +void Game::playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* movingCreature = getCreatureByID(movingCreatureId); + if (!movingCreature) { + return; + } + + Tile* toTile = map.getTile(toPos); + if (!toTile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + playerMoveCreature(player, movingCreature, movingCreatureOrigPos, toTile); +} + +void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile) +{ + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveCreatureByID, + this, player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + player->setNextActionTask(task); + return; + } + + player->setNextActionTask(nullptr); + + if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { + //need to walk to the creature first before moving it + std::forward_list listDir; + if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(1500, std::bind(&Game::playerMoveCreatureByID, this, + player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition())); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + if ((!movingCreature->isPushable() && !player->hasFlag(PlayerFlag_CanPushAllCreatures)) || + (movingCreature->isInGhostMode() && !player->isAccessPlayer())) { + player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + return; + } + + //check throw distance + const Position& movingCreaturePos = movingCreature->getPosition(); + const Position& toPos = toTile->getPosition(); + if ((Position::getDistanceX(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceY(movingCreaturePos, toPos) > movingCreature->getThrowRange()) || (Position::getDistanceZ(movingCreaturePos, toPos) * 4 > movingCreature->getThrowRange())) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + return; + } + + if (player != movingCreature) { + if (toTile->hasFlag(TILESTATE_BLOCKPATH)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } else if ((movingCreature->getZone() == ZONE_PROTECTION && !toTile->hasFlag(TILESTATE_PROTECTIONZONE)) || (movingCreature->getZone() == ZONE_NOPVP && !toTile->hasFlag(TILESTATE_NOPVPZONE))) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } else { + if (CreatureVector* tileCreatures = toTile->getCreatures()) { + for (Creature* tileCreature : *tileCreatures) { + if (!tileCreature->isInGhostMode()) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } + } + } + + Npc* movingNpc = movingCreature->getNpc(); + if (movingNpc && !Spawns::isInZone(movingNpc->getMasterPos(), movingNpc->getMasterRadius(), toPos)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } + } + } + + ReturnValue ret = internalMoveCreature(*movingCreature, *toTile); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + } +} + +ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, uint32_t flags /*= 0*/) +{ + creature->setLastPosition(creature->getPosition()); + const Position& currentPos = creature->getPosition(); + Position destPos = getNextPosition(direction, currentPos); + + bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; + if (creature->getPlayer() && !diagonalMovement) { + //try go up + if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { + Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { + destPos.z--; + internalCreatureTurn(creature, DIRECTION_NORTH); + } + } + } else { + //try go down + Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); + if (currentPos.z != 7 && (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)))) { + tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); + if (tmpTile && tmpTile->hasHeight(3)) { + destPos.z++; + internalCreatureTurn(creature, DIRECTION_SOUTH); + } + } + } + } + + Tile* toTile = map.getTile(destPos); + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; + } + return internalMoveCreature(*creature, *toTile, flags); +} + +ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) +{ + //check if we can move the creature to the destination + ReturnValue ret = toTile.queryAdd(0, creature, 1, flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + map.moveCreature(creature, toTile); + if (creature.getParent() != &toTile) { + return RETURNVALUE_NOERROR; + } + + int32_t index = 0; + Item* toItem = nullptr; + Tile* subCylinder = nullptr; + Tile* toCylinder = &toTile; + Tile* fromCylinder = nullptr; + uint32_t n = 0; + + while ((subCylinder = toCylinder->queryDestination(index, creature, &toItem, flags)) != toCylinder) { + map.moveCreature(creature, *subCylinder); + + if (creature.getParent() != subCylinder) { + //could happen if a script move the creature + fromCylinder = nullptr; + break; + } + + fromCylinder = toCylinder; + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++n >= MAP_MAX_LAYERS) { + break; + } + } + + if (fromCylinder) { + const Position& fromPosition = fromCylinder->getPosition(); + const Position& toPosition = toCylinder->getPosition(); + if (fromPosition.z != toPosition.z && (fromPosition.x != toPosition.x || fromPosition.y != toPosition.y)) { + Direction dir = getDirectionTo(fromPosition, toPosition); + if ((dir & DIRECTION_DIAGONAL_MASK) == 0) { + internalCreatureTurn(&creature, dir); + } + } + } + + return RETURNVALUE_NOERROR; +} + +void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + playerMoveItem(player, fromPos, spriteId, fromStackPos, toPos, count, nullptr, nullptr); +} + +void Game::playerMoveItem(Player* player, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder) +{ + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextActionTask(task); + return; + } + + player->setNextActionTask(nullptr); + + if (item == nullptr) { + uint8_t fromIndex = 0; + if (fromPos.x == 0xFFFF) { + if (fromPos.y & 0x40) { + fromIndex = fromPos.z; + } else { + fromIndex = static_cast(fromPos.y); + } + } else { + fromIndex = fromStackPos; + } + + Thing* thing = internalGetThing(player, fromPos, fromIndex, 0, STACKPOS_MOVE); + if (!thing || !thing->getItem()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + item = thing->getItem(); + } + + if ((item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Cylinder* fromCylinder = internalGetCylinder(player, fromPos); + if (fromCylinder == nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (toCylinder == nullptr) { + toCylinder = internalGetCylinder(player, toPos); + if (toCylinder == nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (!item->isPushable()) { + player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); + return; + } + + const Position& playerPos = player->getPosition(); + const Position& mapFromPos = fromCylinder->getTile()->getPosition(); + if (playerPos.z != mapFromPos.z) { + player->sendCancelMessage(playerPos.z > mapFromPos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + return; + } + + if (!Position::areInRange<1, 1>(playerPos, mapFromPos)) { + //need to walk to the item first before using it + std::forward_list listDir; + if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), fromPos, spriteId, fromStackPos, toPos, count)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + const Tile* toCylinderTile = toCylinder->getTile(); + const Position& mapToPos = toCylinderTile->getPosition(); + + //hangable item specific code + if (item->isHangable() && toCylinderTile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + //destination supports hangable objects so need to move there first + bool vertical = toCylinderTile->hasProperty(CONST_PROP_ISVERTICAL); + if (vertical) { + if (playerPos.x + 1 == mapToPos.x) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } else { // horizontal + if (playerPos.y + 1 == mapToPos.y) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + + if (!Position::areInRange<1, 1, 0>(playerPos, mapToPos)) { + Position walkPos = mapToPos; + if (vertical) { + walkPos.x++; + } else { + walkPos.y++; + } + + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1>(mapFromPos, playerPos) + && !Position::areInRange<1, 1, 0>(mapFromPos, walkPos)) { + //need to pickup the item first + Item* moveItem = nullptr; + + ReturnValue ret = internalMoveItem(fromCylinder, player, INDEX_WHEREEVER, item, count, &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerMoveItemByPlayerID, this, + player->getID(), itemPos, spriteId, itemStackPos, toPos, count)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + } + + if ((Position::getDistanceX(playerPos, mapToPos) > item->getThrowRange()) || + (Position::getDistanceY(playerPos, mapToPos) > item->getThrowRange()) || + (Position::getDistanceZ(mapFromPos, mapToPos) * 4 > item->getThrowRange())) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + return; + } + + if (!canThrowObjectTo(mapFromPos, mapToPos)) { + player->sendCancelMessage(RETURNVALUE_CANNOTTHROW); + return; + } + + uint8_t toIndex = 0; + if (toPos.x == 0xFFFF) { + if (toPos.y & 0x40) { + toIndex = toPos.z; + } else { + toIndex = static_cast(toPos.y); + } + } + + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + } +} + +ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/) +{ + Item* toItem = nullptr; + + Cylinder* subCylinder; + int floorN = 0; + + while ((subCylinder = toCylinder->queryDestination(index, *item, &toItem, flags)) != toCylinder) { + toCylinder = subCylinder; + flags = 0; + + //to prevent infinite loop + if (++floorN >= MAP_MAX_LAYERS) { + break; + } + } + + //destination is the same as the source? + if (item == toItem) { + return RETURNVALUE_NOERROR; //silently ignore move + } + + //check if we can add this item + ReturnValue ret = toCylinder->queryAdd(index, *item, count, flags, actor); + if (ret == RETURNVALUE_NEEDEXCHANGE) { + //check if we can add it to source cylinder + ret = fromCylinder->queryAdd(fromCylinder->getThingIndex(item), *toItem, toItem->getItemCount(), 0); + if (ret == RETURNVALUE_NOERROR) { + //check how much we can move + uint32_t maxExchangeQueryCount = 0; + ReturnValue retExchangeMaxCount = fromCylinder->queryMaxCount(INDEX_WHEREEVER, *toItem, toItem->getItemCount(), maxExchangeQueryCount, 0); + + if (retExchangeMaxCount != RETURNVALUE_NOERROR && maxExchangeQueryCount == 0) { + return retExchangeMaxCount; + } + + if (toCylinder->queryRemove(*toItem, toItem->getItemCount(), flags) == RETURNVALUE_NOERROR) { + int32_t oldToItemIndex = toCylinder->getThingIndex(toItem); + toCylinder->removeThing(toItem, toItem->getItemCount()); + fromCylinder->addThing(toItem); + + if (oldToItemIndex != -1) { + toCylinder->postRemoveNotification(toItem, fromCylinder, oldToItemIndex); + } + + int32_t newToItemIndex = fromCylinder->getThingIndex(toItem); + if (newToItemIndex != -1) { + fromCylinder->postAddNotification(toItem, toCylinder, newToItemIndex); + } + + ret = toCylinder->queryAdd(index, *item, count, flags); + toItem = nullptr; + } + } + } + + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + //check how much we can move + uint32_t maxQueryCount = 0; + ReturnValue retMaxCount = toCylinder->queryMaxCount(index, *item, count, maxQueryCount, flags); + if (retMaxCount != RETURNVALUE_NOERROR && maxQueryCount == 0) { + return retMaxCount; + } + + uint32_t m; + if (item->isStackable()) { + m = std::min(count, maxQueryCount); + } else { + m = maxQueryCount; + } + + Item* moveItem = item; + + //check if we can remove this item + ret = fromCylinder->queryRemove(*item, m, flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (tradeItem) { + if (toCylinder->getItem() == tradeItem) { + return RETURNVALUE_NOTENOUGHROOM; + } + + Cylinder* tmpCylinder = toCylinder->getParent(); + while (tmpCylinder) { + if (tmpCylinder->getItem() == tradeItem) { + return RETURNVALUE_NOTENOUGHROOM; + } + + tmpCylinder = tmpCylinder->getParent(); + } + } + + //remove the item + int32_t itemIndex = fromCylinder->getThingIndex(item); + Item* updateItem = nullptr; + fromCylinder->removeThing(item, m); + + //update item(s) + if (item->isStackable()) { + uint32_t n; + + if (item->equals(toItem)) { + n = std::min(100 - toItem->getItemCount(), m); + toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + updateItem = toItem; + } else { + n = 0; + } + + int32_t newCount = m - n; + if (newCount > 0) { + moveItem = item->clone(); + moveItem->setItemCount(newCount); + } else { + moveItem = nullptr; + } + + if (item->isRemoved()) { + ReleaseItem(item); + } + } + + //add item + if (moveItem /*m - n > 0*/) { + toCylinder->addThing(index, moveItem); + } + + if (itemIndex != -1) { + fromCylinder->postRemoveNotification(item, toCylinder, itemIndex); + } + + if (moveItem) { + int32_t moveItemIndex = toCylinder->getThingIndex(moveItem); + if (moveItemIndex != -1) { + toCylinder->postAddNotification(moveItem, fromCylinder, moveItemIndex); + } + } + + if (updateItem) { + int32_t updateItemIndex = toCylinder->getThingIndex(updateItem); + if (updateItemIndex != -1) { + toCylinder->postAddNotification(updateItem, fromCylinder, updateItemIndex); + } + } + + if (_moveItem) { + if (moveItem) { + *_moveItem = moveItem; + } else { + *_moveItem = item; + } + } + + //we could not move all, inform the player + if (item->isStackable() && maxQueryCount < count) { + return retMaxCount; + } + + return ret; +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index /*= INDEX_WHEREEVER*/, + uint32_t flags/* = 0*/, bool test/* = false*/) +{ + uint32_t remainderCount = 0; + return internalAddItem(toCylinder, item, index, flags, test, remainderCount); +} + +ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount) +{ + if (toCylinder == nullptr || item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Cylinder* destCylinder = toCylinder; + Item* toItem = nullptr; + toCylinder = toCylinder->queryDestination(index, *item, &toItem, flags); + + //check if we can add this item + ReturnValue ret = toCylinder->queryAdd(index, *item, item->getItemCount(), flags); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + /* + Check if we can move add the whole amount, we do this by checking against the original cylinder, + since the queryDestination can return a cylinder that might only hold a part of the full amount. + */ + uint32_t maxQueryCount = 0; + ret = destCylinder->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), maxQueryCount, flags); + + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (test) { + return RETURNVALUE_NOERROR; + } + + if (item->isStackable() && item->equals(toItem)) { + uint32_t m = std::min(item->getItemCount(), maxQueryCount); + uint32_t n = std::min(100 - toItem->getItemCount(), m); + + toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); + + int32_t count = m - n; + if (count > 0) { + if (item->getItemCount() != count) { + Item* remainderItem = item->clone(); + remainderItem->setItemCount(count); + if (internalAddItem(destCylinder, remainderItem, INDEX_WHEREEVER, flags, false) != RETURNVALUE_NOERROR) { + ReleaseItem(remainderItem); + remainderCount = count; + } + } else { + toCylinder->addThing(index, item); + + int32_t itemIndex = toCylinder->getThingIndex(item); + if (itemIndex != -1) { + toCylinder->postAddNotification(item, nullptr, itemIndex); + } + } + } else { + //fully merged with toItem, item will be destroyed + item->onRemoved(); + ReleaseItem(item); + + int32_t itemIndex = toCylinder->getThingIndex(toItem); + if (itemIndex != -1) { + toCylinder->postAddNotification(toItem, nullptr, itemIndex); + } + } + } else { + toCylinder->addThing(index, item); + + int32_t itemIndex = toCylinder->getThingIndex(item); + if (itemIndex != -1) { + toCylinder->postAddNotification(item, nullptr, itemIndex); + } + } + + return RETURNVALUE_NOERROR; +} + +ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool test /*= false*/, uint32_t flags /*= 0*/) +{ + Cylinder* cylinder = item->getParent(); + if (cylinder == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == -1) { + count = item->getItemCount(); + } + + //check if we can remove this item + ReturnValue ret = cylinder->queryRemove(*item, count, flags | FLAG_IGNORENOTMOVEABLE); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (!item->canRemove()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!test) { + int32_t index = cylinder->getThingIndex(item); + + //remove the item + cylinder->removeThing(item, count); + + if (item->isRemoved()) { + ReleaseItem(item); + } + + cylinder->postRemoveNotification(item, nullptr, index); + } + + item->onRemoved(); + return RETURNVALUE_NOERROR; +} + +ReturnValue Game::internalPlayerAddItem(Player* player, Item* item, bool dropOnMap /*= true*/, slots_t slot /*= CONST_SLOT_WHEREEVER*/) +{ + uint32_t remainderCount = 0; + ReturnValue ret = internalAddItem(player, item, static_cast(slot), 0, false, remainderCount); + if (remainderCount != 0) { + Item* remainderItem = Item::CreateItem(item->getID(), remainderCount); + ReturnValue remaindRet = internalAddItem(player->getTile(), remainderItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (remaindRet != RETURNVALUE_NOERROR) { + ReleaseItem(remainderItem); + } + } + + if (ret != RETURNVALUE_NOERROR && dropOnMap) { + ret = internalAddItem(player->getTile(), item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + return ret; +} + +Item* Game::findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch /*= true*/, int32_t subType /*= -1*/) const +{ + if (cylinder == nullptr) { + return nullptr; + } + + std::vector containers; + for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { + Thing* thing = cylinder->getThing(i); + if (!thing) { + continue; + } + + Item* item = thing->getItem(); + if (!item) { + continue; + } + + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } + + if (depthSearch) { + Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + return item; + } + + Container* subContainer = item->getContainer(); + if (subContainer) { + containers.push_back(subContainer); + } + } + } + return nullptr; +} + +bool Game::removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + if (cylinder == nullptr) { + return false; + } + + if (money == 0) { + return true; + } + + std::vector containers; + + std::multimap moneyMap; + uint64_t moneyCount = 0; + + for (size_t i = cylinder->getFirstIndex(), j = cylinder->getLastIndex(); i < j; ++i) { + Thing* thing = cylinder->getThing(i); + if (!thing) { + continue; + } + + Item* item = thing->getItem(); + if (!item) { + continue; + } + + Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } else { + const uint32_t worth = item->getWorth(); + if (worth != 0) { + moneyCount += worth; + moneyMap.emplace(worth, item); + } + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } else { + const uint32_t worth = item->getWorth(); + if (worth != 0) { + moneyCount += worth; + moneyMap.emplace(worth, item); + } + } + } + } + + if (moneyCount < money) { + return false; + } + + for (const auto& moneyEntry : moneyMap) { + Item* item = moneyEntry.second; + if (moneyEntry.first < money) { + internalRemoveItem(item); + money -= moneyEntry.first; + } else if (moneyEntry.first > money) { + const uint32_t worth = moneyEntry.first / item->getItemCount(); + const uint32_t removeCount = std::ceil(money / static_cast(worth)); + + addMoney(cylinder, (worth * removeCount) - money, flags); + internalRemoveItem(item, removeCount); + break; + } else { + internalRemoveItem(item); + break; + } + } + return true; +} + +void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/) +{ + if (money == 0) { + return; + } + + uint32_t crystalCoins = money / 10000; + money -= crystalCoins * 10000; + while (crystalCoins > 0) { + const uint16_t count = std::min(100, crystalCoins); + + Item* remaindItem = Item::CreateItem(ITEM_CRYSTAL_COIN, count); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + crystalCoins -= count; + } + + uint16_t platinumCoins = money / 100; + if (platinumCoins != 0) { + Item* remaindItem = Item::CreateItem(ITEM_PLATINUM_COIN, platinumCoins); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + + money -= platinumCoins * 100; + } + + if (money != 0) { + Item* remaindItem = Item::CreateItem(ITEM_GOLD_COIN, money); + + ReturnValue ret = internalAddItem(cylinder, remaindItem, INDEX_WHEREEVER, flags); + if (ret != RETURNVALUE_NOERROR) { + internalAddItem(cylinder->getTile(), remaindItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + } + } +} + +Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) +{ + if (item->getID() == newId && (newCount == -1 || (newCount == item->getSubType() && newCount != 0))) { //chargeless item placed on map = infinite + return item; + } + + Cylinder* cylinder = item->getParent(); + if (cylinder == nullptr) { + return nullptr; + } + + int32_t itemIndex = cylinder->getThingIndex(item); + if (itemIndex == -1) { + return item; + } + + if (!item->canTransform()) { + return item; + } + + const ItemType& newType = Item::items[newId]; + if (newType.id == 0) { + return item; + } + + const ItemType& curType = Item::items[item->getID()]; + if (curType.alwaysOnTop != newType.alwaysOnTop) { + //This only occurs when you transform items on tiles from a downItem to a topItem (or vice versa) + //Remove the old, and add the new + cylinder->removeThing(item, item->getItemCount()); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + + item->setID(newId); + if (newCount != -1) { + item->setSubType(newCount); + } + cylinder->addThing(item); + + Cylinder* newParent = item->getParent(); + if (newParent == nullptr) { + ReleaseItem(item); + return nullptr; + } + + newParent->postAddNotification(item, cylinder, newParent->getThingIndex(item)); + return item; + } + + if (curType.type == newType.type) { + //Both items has the same type so we can safely change id/subtype + if (newCount == 0 && (item->isStackable() || item->hasAttribute(ITEM_ATTRIBUTE_CHARGES))) { + if (item->isStackable()) { + internalRemoveItem(item); + return nullptr; + } else { + int32_t newItemId = newId; + if (curType.id == newType.id) { + newItemId = curType.decayTo; + } + + if (newItemId < 0) { + internalRemoveItem(item); + return nullptr; + } else if (newItemId != newId) { + //Replacing the the old item with the new while maintaining the old position + Item* newItem = Item::CreateItem(newItemId, 1); + if (newItem == nullptr) { + return nullptr; + } + + cylinder->replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(nullptr); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + ReleaseItem(item); + return newItem; + } else { + return transformItem(item, newItemId); + } + } + } else { + cylinder->postRemoveNotification(item, cylinder, itemIndex); + uint16_t itemId = item->getID(); + int32_t count = item->getSubType(); + + if (curType.id != newType.id) { + if (newType.group != curType.group) { + item->setDefaultSubtype(); + } + + itemId = newId; + } + + if (newCount != -1 && newType.hasSubType()) { + count = newCount; + } + + cylinder->updateThing(item, itemId, count); + cylinder->postAddNotification(item, cylinder, itemIndex); + return item; + } + } + + //Replacing the the old item with the new while maintaining the old position + Item* newItem; + if (newCount == -1) { + newItem = Item::CreateItem(newId); + } else { + newItem = Item::CreateItem(newId, newCount); + } + + if (newItem == nullptr) { + return nullptr; + } + + cylinder->replaceThing(itemIndex, newItem); + cylinder->postAddNotification(newItem, cylinder, itemIndex); + + item->setParent(nullptr); + cylinder->postRemoveNotification(item, cylinder, itemIndex); + ReleaseItem(item); + + return newItem; +} + +ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pushMove/* = true*/, uint32_t flags /*= 0*/) +{ + if (newPos == thing->getPosition()) { + return RETURNVALUE_NOERROR; + } else if (thing->isRemoved()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Tile* toTile = map.getTile(newPos); + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (Creature* creature = thing->getCreature()) { + ReturnValue ret = toTile->queryAdd(0, *creature, 1, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + Position fromPos = creature->getPosition(); + if (Position::getOffsetX(fromPos, newPos) <= 0) { + if (Position::getOffsetX(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_EAST); + } else if (Position::getOffsetY(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_SOUTH); + } else if (Position::getOffsetY(fromPos, newPos) > 0) { + internalCreatureTurn(creature, DIRECTION_NORTH); + } + } else { + internalCreatureTurn(creature, DIRECTION_WEST); + } + + map.moveCreature(*creature, *toTile, !pushMove); + return RETURNVALUE_NOERROR; + } else if (Item* item = thing->getItem()) { + return internalMoveItem(item->getParent(), toTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr, flags); + } + return RETURNVALUE_NOTPOSSIBLE; +} + +//Implementation of player invoked events +void Game::playerMove(uint32_t playerId, Direction direction) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + player->setNextWalkActionTask(nullptr); + + player->startAutoWalk(std::forward_list { direction }); +} + +bool Game::playerBroadcastMessage(Player* player, const std::string& text) const +{ + if (!player->hasFlag(PlayerFlag_CanBroadcast)) { + return false; + } + + std::cout << "> " << player->getName() << " broadcasted: \"" << text << "\"." << std::endl; + + for (const auto& it : players) { + it.second->sendPrivateMessage(player, TALKTYPE_BROADCAST, text); + } + + return true; +} + +void Game::playerCreatePrivateChannel(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player || !player->isPremium()) { + return; + } + + ChatChannel* channel = g_chat->createChannel(*player, CHANNEL_PRIVATE); + if (!channel || !channel->addUser(*player)) { + return; + } + + player->sendCreatePrivateChannel(channel->getId(), channel->getName()); +} + +void Game::playerChannelInvite(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); + if (!channel) { + return; + } + + Player* invitePlayer = getPlayerByName(name); + if (!invitePlayer) { + return; + } + + if (player == invitePlayer) { + return; + } + + channel->invitePlayer(*player, *invitePlayer); +} + +void Game::playerChannelExclude(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + PrivateChatChannel* channel = g_chat->getPrivateChannel(*player); + if (!channel) { + return; + } + + Player* excludePlayer = getPlayerByName(name); + if (!excludePlayer) { + return; + } + + if (player == excludePlayer) { + return; + } + + channel->excludePlayer(*player, *excludePlayer); +} + +void Game::playerRequestChannels(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendChannelsDialog(); +} + +void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + ChatChannel* channel = g_chat->addUserToChannel(*player, channelId); + if (!channel) { + return; + } + + if (channel->getId() == CHANNEL_RULE_REP) { + player->sendRuleViolationsChannel(channel->getId()); + } else { + player->sendChannel(channel->getId(), channel->getName()); + } +} + +void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + g_chat->removeUserFromChannel(*player, channelId); +} + +void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!IOLoginData::formatPlayerName(receiver)) { + player->sendCancelMessage("A player with this name does not exist."); + return; + } + + player->sendOpenPrivateChannel(receiver); +} + +void Game::playerReceivePing(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->receivePing(); +} + +void Game::playerReceivePingBack(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendPingBack(); +} + +void Game::playerAutoWalk(uint32_t playerId, const std::forward_list& listDir) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + player->setNextWalkTask(nullptr); + player->startAutoWalk(listDir); +} + +void Game::playerStopAutoWalk(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->stopWalk(); +} + +void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint16_t fromSpriteId, + const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, fromSpriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != fromSpriteId) || (!item->isDisguised() && item->getID() != fromSpriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + if (ret == RETURNVALUE_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + if (ret == RETURNVALUE_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && toPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && + !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = nullptr; + + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItemEx, this, + playerId, itemPos, itemStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItemEx, this, + playerId, fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItemEx(player, fromPos, toPos, toStackPos, item); +} + +void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint8_t index, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, spriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + ReturnValue ret = g_actions->canUse(player, pos); + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId)); + player->setNextWalkActionTask(task); + return; + } + + ret = RETURNVALUE_THEREISNOWAY; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseItem, this, + playerId, pos, stackPos, index, spriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItem(player, pos, index, item); +} + +void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + if (creature->getPlayer()) { + player->sendCancelMessage(RETURNVALUE_DIRECTPLAYERSHOOT); + return; + } + + if (!Position::areInRange<7, 5, 0>(creature->getPosition(), player->getPosition())) { + return; + } + + Thing* thing = internalGetThing(player, fromPos, fromStackPos, spriteId, STACKPOS_USEITEM); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); + return; + } + + Position toPos = creature->getPosition(); + Position walkToPos = fromPos; + ReturnValue ret = g_actions->canUse(player, fromPos); + if (ret == RETURNVALUE_NOERROR) { + ret = g_actions->canUse(player, toPos, item); + if (ret == RETURNVALUE_TOOFARAWAY) { + walkToPos = toPos; + } + } + + if (ret != RETURNVALUE_NOERROR) { + if (ret == RETURNVALUE_TOOFARAWAY) { + Position itemPos = fromPos; + uint8_t itemStackPos = fromStackPos; + + if (fromPos.x != 0xFFFF && Position::areInRange<1, 1, 0>(fromPos, player->getPosition()) && !Position::areInRange<1, 1, 0>(fromPos, toPos)) { + Item* moveItem = nullptr; + ret = internalMoveItem(item->getParent(), player, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + return; + } + + //changing the position since its now in the inventory of the player + internalGetPosition(moveItem, itemPos, itemStackPos); + } + + std::forward_list listDir; + if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerUseWithCreature, this, + playerId, itemPos, itemStackPos, creatureId, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + player->sendCancelMessage(ret); + return; + } + + if (!player->canDoAction()) { + uint32_t delay = player->getNextActionTime(); + SchedulerTask* task = createSchedulerTask(delay, std::bind(&Game::playerUseWithCreature, this, + playerId, fromPos, fromStackPos, creatureId, spriteId)); + player->setNextActionTask(task); + return; + } + + player->resetIdleTime(); + player->setNextActionTask(nullptr); + + g_actions->useItemEx(player, fromPos, creature->getPosition(), creature->getParent()->getThingIndex(creature), item, creature); +} + +void Game::playerCloseContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->closeContainer(cid); + player->sendCloseContainer(cid); +} + +void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(cid); + if (!container) { + return; + } + + Container* parentContainer = dynamic_cast(container->getRealParent()); + if (!parentContainer) { + return; + } + + player->addContainer(cid, parentContainer); + player->sendContainer(cid, parentContainer, parentContainer->hasParent(), player->getContainerIndex(cid)); +} + +void Game::playerUpdateContainer(uint32_t playerId, uint8_t cid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(cid); + if (!container) { + return; + } + + player->sendContainer(cid, container, container->hasParent(), player->getContainerIndex(cid)); +} + +void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!thing) { + return; + } + + Item* item = thing->getItem(); + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || !item->isRotatable() || (!item->isDisguised() && item->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRotateItem, this, + playerId, pos, stackPos, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + uint16_t newId = Item::items[item->getID()].rotateTo; + if (newId != 0) { + transformItem(item, newId); + } +} + +void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint16_t maxTextLength = 0; + uint32_t internalWindowTextId = 0; + + Item* writeItem = player->getWriteItem(internalWindowTextId, maxTextLength); + if (text.length() > maxTextLength || windowTextId != internalWindowTextId) { + return; + } + + if (!writeItem || writeItem->isRemoved()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Cylinder* topParent = writeItem->getTopParent(); + + Player* owner = dynamic_cast(topParent); + if (owner && owner != player) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (!Position::areInRange<1, 1, 0>(writeItem->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + if (!text.empty()) { + if (writeItem->getText() != text) { + writeItem->setText(text); + writeItem->setWriter(player->getName()); + writeItem->setDate(time(nullptr)); + } + } else { + writeItem->resetText(); + writeItem->resetWriter(); + writeItem->resetDate(); + } + + uint16_t newId = Item::items[writeItem->getID()].writeOnceItemId; + if (newId != 0) { + transformItem(writeItem, newId); + } + + player->setWriteItem(nullptr); +} + +void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Container* container = player->getContainerByID(containerId); + if (!container) { + return; + } + + if ((index % container->capacity()) != 0 || index >= container->size()) { + return; + } + + player->setContainerIndex(containerId, index); + player->sendContainer(containerId, container, container->hasParent(), index); +} + +void Game::playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + uint32_t internalWindowTextId; + uint32_t internalListId; + + House* house = player->getEditHouse(internalWindowTextId, internalListId); + if (house && house->canEditAccessList(internalListId, player) && internalWindowTextId == windowTextId && listId == 0) { + house->setAccessList(internalListId, text); + } + + player->setEditHouse(nullptr); +} + +void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* tradePartner = getPlayerByID(tradePlayerId); + if (!tradePartner || tradePartner == player) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "Sorry, not possible."); + return; + } + + if (!Position::areInRange<2, 2, 0>(tradePartner->getPosition(), player->getPosition())) { + std::ostringstream ss; + ss << tradePartner->getName() << " tells you to move closer."; + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + return; + } + + Thing* tradeThing = internalGetThing(player, pos, stackPos, 0, STACKPOS_TOPDOWN_ITEM); + if (!tradeThing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Item* tradeItem = tradeThing->getItem(); + if (!tradeItem->isPickupable() || (tradeItem->isDisguised() && tradeItem->getDisguiseId() != spriteId) || (!tradeItem->isDisguised() && tradeItem->getID() != spriteId)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + const Position& playerPosition = player->getPosition(); + const Position& tradeItemPosition = tradeItem->getPosition(); + if (playerPosition.z != tradeItemPosition.z) { + player->sendCancelMessage(playerPosition.z > tradeItemPosition.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + return; + } + + if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + + SchedulerTask* task = createSchedulerTask(400, std::bind(&Game::playerRequestTrade, this, + playerId, pos, stackPos, tradePlayerId, spriteId)); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + Container* tradeItemContainer = tradeItem->getContainer(); + if (tradeItemContainer) { + for (const auto& it : tradeItems) { + Item* item = it.first; + if (tradeItem == item) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + if (tradeItemContainer->isHoldingItem(item)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + } + } else { + for (const auto& it : tradeItems) { + Item* item = it.first; + if (tradeItem == item) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + + Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "This item is already being traded."); + return; + } + } + } + + Container* tradeContainer = tradeItem->getContainer(); + if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > 100) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "You can not trade more than 100 items."); + return; + } + + internalStartTrade(player, tradePartner, tradeItem); +} + +bool Game::internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem) +{ + if (player->tradeState != TRADE_NONE && !(player->tradeState == TRADE_ACKNOWLEDGE && player->tradePartner == tradePartner)) { + player->sendCancelMessage(RETURNVALUE_YOUAREALREADYTRADING); + return false; + } else if (tradePartner->tradeState != TRADE_NONE && tradePartner->tradePartner != player) { + player->sendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING); + return false; + } + + player->tradePartner = tradePartner; + player->tradeItem = tradeItem; + player->tradeState = TRADE_INITIATED; + tradeItem->incrementReferenceCounter(); + tradeItems[tradeItem] = player->getID(); + + player->sendTradeItemRequest(player->getName(), tradeItem, true); + + if (tradePartner->tradeState == TRADE_NONE) { + std::ostringstream ss; + ss << player->getName() << " wants to trade with you."; + tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + tradePartner->tradeState = TRADE_ACKNOWLEDGE; + tradePartner->tradePartner = player; + } else { + Item* counterOfferItem = tradePartner->tradeItem; + player->sendTradeItemRequest(tradePartner->getName(), counterOfferItem, false); + tradePartner->sendTradeItemRequest(player->getName(), tradeItem, false); + } + + return true; +} + +void Game::playerAcceptTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!(player->getTradeState() == TRADE_ACKNOWLEDGE || player->getTradeState() == TRADE_INITIATED)) { + return; + } + + Player* tradePartner = player->tradePartner; + if (!tradePartner) { + return; + } + + if (!canThrowObjectTo(tradePartner->getPosition(), player->getPosition())) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + return; + } + + player->setTradeState(TRADE_ACCEPT); + + if (tradePartner->getTradeState() == TRADE_ACCEPT) { + Item* tradeItem1 = player->tradeItem; + Item* tradeItem2 = tradePartner->tradeItem; + + player->setTradeState(TRADE_TRANSFER); + tradePartner->setTradeState(TRADE_TRANSFER); + + auto it = tradeItems.find(tradeItem1); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + it = tradeItems.find(tradeItem2); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + bool isSuccess = false; + + ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); + ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true); + if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { + ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true); + ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true); + if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { + Cylinder* cylinder1 = tradeItem1->getParent(); + Cylinder* cylinder2 = tradeItem2->getParent(); + + uint32_t count1 = tradeItem1->getItemCount(); + uint32_t count2 = tradeItem2->getItemCount(); + + ret1 = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, tradeItem2); + if (ret1 == RETURNVALUE_NOERROR) { + internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, nullptr, FLAG_IGNOREAUTOSTACK); + + tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player); + + isSuccess = true; + } + } + } + + if (!isSuccess) { + std::string errorDescription; + + if (tradePartner->tradeItem) { + errorDescription = getTradeErrorDescription(ret1, tradeItem1); + tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + } + + if (player->tradeItem) { + errorDescription = getTradeErrorDescription(ret2, tradeItem2); + player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + } + } + + player->setTradeState(TRADE_NONE); + player->tradeItem = nullptr; + player->tradePartner = nullptr; + player->sendTradeClose(); + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradeItem = nullptr; + tradePartner->tradePartner = nullptr; + tradePartner->sendTradeClose(); + } +} + +std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) +{ + if (item) { + if (ret == RETURNVALUE_NOTENOUGHCAPACITY) { + std::ostringstream ss; + ss << "You do not have enough capacity to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object."; + } + + ss << std::endl << ' ' << item->getWeightDescription(); + return ss.str(); + } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { + std::ostringstream ss; + ss << "You do not have enough room to carry"; + + if (item->isStackable() && item->getItemCount() > 1) { + ss << " these objects."; + } else { + ss << " this object."; + } + + return ss.str(); + } + } + return "Trade could not be completed."; +} + +void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* tradePartner = player->tradePartner; + if (!tradePartner) { + return; + } + + Item* tradeItem; + if (lookAtCounterOffer) { + tradeItem = tradePartner->getTradeItem(); + } else { + tradeItem = player->getTradeItem(); + } + + if (!tradeItem) { + return; + } + + const Position& playerPosition = player->getPosition(); + const Position& tradeItemPosition = tradeItem->getPosition(); + + int32_t lookDistance = std::max(Position::getDistanceX(playerPosition, tradeItemPosition), + Position::getDistanceY(playerPosition, tradeItemPosition)); + + std::stringstream ss; + if (index == 0) { + ss << "You see " << tradeItem->getDescription(lookDistance); + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + + Container* tradeContainer = tradeItem->getContainer(); + if (!tradeContainer) { + return; + } + + std::vector containers {tradeContainer}; + size_t i = 0; + while (i < containers.size()) { + const Container* container = containers[i++]; + for (Item* item : container->getItemList()) { + Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } + + if (--index == 0) { + ss << "You see " << item->getDescription(lookDistance); + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + } + } +} + +void Game::playerCloseTrade(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + internalCloseTrade(player); +} + +void Game::internalCloseTrade(Player* player) +{ + Player* tradePartner = player->tradePartner; + if ((tradePartner && tradePartner->getTradeState() == TRADE_TRANSFER) || player->getTradeState() == TRADE_TRANSFER) { + return; + } + + if (player->getTradeItem()) { + auto it = tradeItems.find(player->getTradeItem()); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); + player->tradeItem = nullptr; + } + + player->setTradeState(TRADE_NONE); + player->tradePartner = nullptr; + + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + player->sendTradeClose(); + + if (tradePartner) { + if (tradePartner->getTradeItem()) { + auto it = tradeItems.find(tradePartner->getTradeItem()); + if (it != tradeItems.end()) { + ReleaseItem(it->first); + tradeItems.erase(it); + } + + tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); + tradePartner->tradeItem = nullptr; + } + + tradePartner->setTradeState(TRADE_NONE); + tradePartner->tradePartner = nullptr; + + tradePartner->sendTextMessage(MESSAGE_STATUS_SMALL, "Trade cancelled."); + tradePartner->sendTradeClose(); + } +} + +void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Thing* thing = internalGetThing(player, pos, stackPos, 0, STACKPOS_LOOK); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Position thingPos = thing->getPosition(); + if (!player->canSee(thingPos)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + + Position playerPos = player->getPosition(); + + int32_t lookDistance; + if (thing != player) { + lookDistance = std::max(Position::getDistanceX(playerPos, thingPos), Position::getDistanceY(playerPos, thingPos)); + if (playerPos.z != thingPos.z) { + lookDistance += 15; + } + } else { + lookDistance = -1; + } + + std::ostringstream ss; + ss << "You see " << thing->getDescription(lookDistance); + + if (player->isAccessPlayer()) { + Item* item = thing->getItem(); + if (item) { + ss << std::endl << "ItemID: [" << item->getID() << ']'; + + uint16_t actionId = item->getActionId(); + if (actionId != 0) { + ss << ", ActionID: [" << actionId << ']'; + } + + uint16_t movementID = item->getMovementId(); + if (movementID != 0) { + ss << ", MovementID: [" << movementID << ']'; + } + + ss << '.'; + const ItemType& it = Item::items[item->getID()]; + + if (it.transformEquipTo) { + ss << std::endl << "TransformTo: [" << it.transformEquipTo << "] (onEquip)."; + } else if (it.transformDeEquipTo) { + ss << std::endl << "TransformTo: [" << it.transformDeEquipTo << "] (onDeEquip)."; + } + + if (it.decayTo != -1) { + ss << std::endl << "DecayTo: [" << it.decayTo << "]."; + } + } + + if (const Creature* creature = thing->getCreature()) { + ss << std::endl << "Health: [" << creature->getHealth() << " / " << creature->getMaxHealth() << ']'; + + if (creature->getMaxMana() > 0) { + ss << ", Mana: [" << creature->getMana() << " / " << creature->getMaxMana() << ']'; + } + + ss << '.'; + } + + ss << std::endl << "Position: [X: " << thingPos.x << "] [Y: " << thingPos.y << "] [Z: " << thingPos.getZ() << "]."; + } + + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + +void Game::playerLookInBattleList(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + if (!player->canSeeCreature(creature)) { + return; + } + + const Position& creaturePos = creature->getPosition(); + if (!player->canSee(creaturePos)) { + return; + } + + int32_t lookDistance; + if (creature != player) { + const Position& playerPos = player->getPosition(); + lookDistance = std::max(Position::getDistanceX(playerPos, creaturePos), Position::getDistanceY(playerPos, creaturePos)); + if (playerPos.z != creaturePos.z) { + lookDistance += 15; + } + } else { + lookDistance = -1; + } + + std::ostringstream ss; + ss << "You see " << creature->getDescription(lookDistance); + + if (player->isAccessPlayer()) { + ss << std::endl << "Health: [" << creature->getHealth() << " / " << creature->getMaxHealth() << ']'; + + if (creature->getMaxMana() > 0) { + ss << ", Mana: [" << creature->getMana() << " / " << creature->getMaxMana() << ']'; + } + + ss << '.' << std::endl; + ss << "Position: [X: " << creaturePos.x << "] [Y: " << creaturePos.y << "] [Z: " << creaturePos.getZ() << "]."; + } + + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + +void Game::playerCancelAttackAndFollow(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + playerSetAttackedCreature(playerId, 0); + playerFollowCreature(playerId, 0); + player->stopWalk(); +} + +void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->getAttackedCreature() && creatureId == 0) { + player->setAttackedCreature(nullptr); + player->sendCancelTarget(); + return; + } + + Creature* attackCreature = getCreatureByID(creatureId); + if (!attackCreature) { + player->setAttackedCreature(nullptr); + player->sendCancelTarget(); + return; + } + + ReturnValue ret = Combat::canTargetCreature(player, attackCreature); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + player->sendCancelTarget(); + player->setAttackedCreature(nullptr); + return; + } + + player->setAttackedCreature(attackCreature); + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); +} + +void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->setAttackedCreature(nullptr); + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, this, player->getID()))); + player->setFollowCreature(getCreatureByID(creatureId)); +} + +void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->setFightMode(fightMode); + player->setChaseMode(chaseMode); + player->setSecureMode(secureMode); +} + +void Game::playerRequestAddVip(uint32_t playerId, const std::string& name) +{ + if (name.length() > 20) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* vipPlayer = getPlayerByName(name); + if (!vipPlayer) { + uint32_t guid; + bool specialVip; + std::string formattedName = name; + if (!IOLoginData::getGuidByNameEx(guid, specialVip, formattedName)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name does not exist."); + return; + } + + if (specialVip && !player->hasFlag(PlayerFlag_SpecialVIP)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); + return; + } + + player->addVIP(guid, formattedName, VIPSTATUS_OFFLINE); + } else { + if (vipPlayer->hasFlag(PlayerFlag_SpecialVIP) && !player->hasFlag(PlayerFlag_SpecialVIP)) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You can not add this player."); + return; + } + + if (!vipPlayer->isInGhostMode() || player->isAccessPlayer()) { + player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_ONLINE); + } else { + player->addVIP(vipPlayer->getGUID(), vipPlayer->getName(), VIPSTATUS_OFFLINE); + } + } +} + +void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->removeVIP(guid); +} + +void Game::playerTurn(uint32_t playerId, Direction dir) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + internalCreatureTurn(player, dir); +} + +void Game::playerRequestOutfit(uint32_t playerId) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendOutfitWindow(); +} + +void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) +{ + if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->canWear(outfit.lookType)) { + player->defaultOutfit = outfit; + + if (player->hasCondition(CONDITION_OUTFIT)) { + return; + } + + internalCreatureChangeOutfit(player, outfit); + } +} + +void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->resetIdleTime(); + + uint32_t muteTime = player->isMuted(); + if (muteTime > 0) { + std::ostringstream ss; + ss << "You are still muted for " << muteTime << " seconds."; + player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + return; + } + + if (playerSayCommand(player, text)) { + return; + } + + if (playerSaySpell(player, type, text)) { + return; + } + + if (!text.empty() && text.front() == '/' && player->isAccessPlayer()) { + return; + } + + player->removeMessageBuffer(); + + switch (type) { + case TALKTYPE_SAY: + internalCreatureSay(player, TALKTYPE_SAY, text, false); + break; + + case TALKTYPE_WHISPER: + playerWhisper(player, text); + break; + + case TALKTYPE_YELL: + playerYell(player, text); + break; + + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: + playerSpeakTo(player, type, receiver, text); + break; + + case TALKTYPE_CHANNEL_O: + case TALKTYPE_CHANNEL_Y: + case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: + if (channelId == CHANNEL_RULE_REP) { + playerSay(playerId, 0, TALKTYPE_SAY, receiver, text); + } else { + g_chat->talkToChannel(*player, type, text, channelId); + } + break; + + case TALKTYPE_BROADCAST: + playerBroadcastMessage(player, text); + break; + + case TALKTYPE_RVR_CHANNEL: + playerReportRuleViolationReport(player, text); + break; + + case TALKTYPE_RVR_CONTINUE: + playerContinueRuleViolationReport(player, text); + break; + + default: + break; + } +} + +bool Game::playerSayCommand(Player* player, const std::string& text) +{ + if (text.empty()) { + return false; + } + + char firstCharacter = text.front(); + for (char commandTag : commandTags) { + if (commandTag == firstCharacter) { + if (commands.exeCommand(*player, text)) { + return true; + } + } + } + return false; +} + +bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& text) +{ + std::string words = text; + + TalkActionResult_t result = g_talkActions->playerSaySpell(player, type, words); + if (result == TALKACTION_BREAK) { + return true; + } + + result = g_spells->playerSaySpell(player, words); + if (result == TALKACTION_BREAK) { + return internalCreatureSay(player, TALKTYPE_SAY, text, false); + } else if (result == TALKACTION_FAILED) { + return true; + } + + return false; +} + +void Game::playerWhisper(Player* player, const std::string& text) +{ + SpectatorVec list; + map.getSpectators(list, player->getPosition(), false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + + //send to client + for (Creature* spectator : list) { + if (Player* spectatorPlayer = spectator->getPlayer()) { + if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { + spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); + } else { + spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, text); + } + } + } + + //event method + for (Creature* spectator : list) { + spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); + } +} + +bool Game::playerYell(Player* player, const std::string& text) +{ + if (player->getLevel() == 1) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "You may not yell as long as you are on level 1."); + return false; + } + + if (player->hasCondition(CONDITION_YELLTICKS)) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + return false; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_YELLTICKS, 30000, 0); + player->addCondition(condition); + } + + internalCreatureSay(player, TALKTYPE_YELL, asUpperCaseString(text), false); + return true; +} + +bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, + const std::string& text) +{ + Player* toPlayer = getPlayerByName(receiver); + if (!toPlayer) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); + return false; + } + + if (type == TALKTYPE_PRIVATE_RED && (!player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER)) { + type = TALKTYPE_PRIVATE; + } + + toPlayer->sendPrivateMessage(player, type, text); + toPlayer->onCreatureSay(player, type, text); + + if (toPlayer->isInGhostMode() && !player->isAccessPlayer()) { + player->sendTextMessage(MESSAGE_STATUS_SMALL, "A player with this name is not online."); + } else { + std::ostringstream ss; + ss << "Message sent to " << toPlayer->getName() << '.'; + player->sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + } + return true; +} + +//-- +bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const +{ + return map.canThrowObjectTo(fromPos, toPos, checkLineOfSight, rangex, rangey); +} + +bool Game::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +{ + return map.isSightClear(fromPos, toPos, floorCheck); +} + +bool Game::internalCreatureTurn(Creature* creature, Direction dir) +{ + if (creature->getDirection() == dir) { + return false; + } + + creature->setDirection(dir); + + //send to client + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureTurn(creature); + } + return true; +} + +bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* listPtr/* = nullptr*/, const Position* pos/* = nullptr*/) +{ + if (text.empty()) { + return false; + } + + if (!pos) { + pos = &creature->getPosition(); + } + + SpectatorVec list; + + if (!listPtr || listPtr->empty()) { + // This somewhat complex construct ensures that the cached SpectatorVec + // is used if available and if it can be used, else a local vector is + // used (hopefully the compiler will optimize away the construction of + // the temporary when it's not used). + if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { + map.getSpectators(list, *pos, false, false, + Map::maxClientViewportX, Map::maxClientViewportX, + Map::maxClientViewportY, Map::maxClientViewportY); + } else { + map.getSpectators(list, *pos, true, false, 18, 18, 14, 14); + } + } else { + list = (*listPtr); + } + + //send to client + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { + tmpPlayer->sendCreatureSay(creature, type, text, pos); + } + } + } + + //event method + for (Creature* spectator : list) { + spectator->onCreatureSay(creature, type, text); + } + return true; +} + +void Game::checkCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->onWalk(); + cleanup(); + } +} + +void Game::updateCreatureWalk(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->goToFollowCreature(); + } +} + +void Game::checkCreatureAttack(uint32_t creatureId) +{ + Creature* creature = getCreatureByID(creatureId); + if (creature && creature->getHealth() > 0) { + creature->onAttacking(0); + } +} + +void Game::addCreatureCheck(Creature* creature) +{ + creature->creatureCheck = true; + + if (creature->inCheckCreaturesVector) { + // already in a vector + return; + } + + creature->inCheckCreaturesVector = true; + checkCreatureLists[uniform_random(0, EVENT_CREATURECOUNT - 1)].push_back(creature); + creature->incrementReferenceCounter(); +} + +void Game::removeCreatureCheck(Creature* creature) +{ + if (creature->inCheckCreaturesVector) { + creature->creatureCheck = false; + } +} + +void Game::checkCreatures(size_t index) +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_CHECK_CREATURE_INTERVAL, std::bind(&Game::checkCreatures, this, (index + 1) % EVENT_CREATURECOUNT))); + + auto& checkCreatureList = checkCreatureLists[index]; + auto it = checkCreatureList.begin(), end = checkCreatureList.end(); + while (it != end) { + Creature* creature = *it; + if (creature->creatureCheck) { + if (creature->getHealth() > 0) { + creature->onThink(EVENT_CREATURE_THINK_INTERVAL); + creature->onAttacking(EVENT_CREATURE_THINK_INTERVAL); + creature->executeConditions(EVENT_CREATURE_THINK_INTERVAL); + } else { + creature->onDeath(); + } + ++it; + } else { + creature->inCheckCreaturesVector = false; + it = checkCreatureList.erase(it); + ReleaseCreature(creature); + } + } + + cleanup(); +} + +void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) +{ + creature->setSpeed(varSpeedDelta); + + //send to clients + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), false, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); + } +} + +void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit) +{ + creature->setCurrentOutfit(outfit); + + if (creature->isInvisible()) { + return; + } + + //send to clients + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); + } +} + +void Game::internalCreatureChangeVisible(Creature* creature, bool visible) +{ + //send to clients + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); + } +} + +void Game::changeLight(const Creature* creature) +{ + //send to clients + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureLight(creature); + } +} + +bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field) +{ + if (damage.type == COMBAT_NONE) { + return true; + } + + if (target->getPlayer() && target->isInGhostMode()) { + return true; + } + + if (damage.value > 0) { + return false; + } + + static const auto sendBlockEffect = [this](BlockType_t blockType, CombatType_t combatType, const Position& targetPos) { + if (blockType == BLOCK_DEFENSE) { + addMagicEffect(targetPos, CONST_ME_POFF); + } else if (blockType == BLOCK_ARMOR) { + addMagicEffect(targetPos, CONST_ME_BLOCKHIT); + } else if (blockType == BLOCK_IMMUNITY) { + uint8_t hitEffect = 0; + switch (combatType) { + case COMBAT_UNDEFINEDDAMAGE: { + return; + } + case COMBAT_ENERGYDAMAGE: + case COMBAT_FIREDAMAGE: + case COMBAT_PHYSICALDAMAGE: { + hitEffect = CONST_ME_BLOCKHIT; + break; + } + case COMBAT_EARTHDAMAGE: { + hitEffect = CONST_ME_GREEN_RINGS; + break; + } + default: { + hitEffect = CONST_ME_POFF; + break; + } + } + addMagicEffect(targetPos, hitEffect); + } + }; + + BlockType_t primaryBlockType; + if (damage.type != COMBAT_NONE) { + damage.value = -damage.value; + primaryBlockType = target->blockHit(attacker, damage.type, damage.value, checkDefense, checkArmor, field); + + damage.value = -damage.value; + sendBlockEffect(primaryBlockType, damage.type, target->getPosition()); + } else { + primaryBlockType = BLOCK_NONE; + } + + return (primaryBlockType != BLOCK_NONE); +} + +void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect) +{ + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: { + Item* splash = nullptr; + switch (target->getRace()) { + case RACE_VENOM: + color = TEXTCOLOR_LIGHTGREEN; + effect = CONST_ME_HITBYPOISON; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_SLIME); + break; + case RACE_BLOOD: + color = TEXTCOLOR_RED; + effect = CONST_ME_DRAWBLOOD; + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); + break; + case RACE_UNDEAD: + color = TEXTCOLOR_LIGHTGREY; + effect = CONST_ME_HITAREA; + break; + case RACE_FIRE: + color = TEXTCOLOR_ORANGE; + effect = CONST_ME_DRAWBLOOD; + break; + default: + color = TEXTCOLOR_NONE; + effect = CONST_ME_NONE; + break; + } + + if (splash) { + internalAddItem(target->getTile(), splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + startDecay(splash); + } + + break; + } + + case COMBAT_ENERGYDAMAGE: { + color = TEXTCOLOR_LIGHTBLUE; + effect = CONST_ME_ENERGYHIT; + break; + } + + case COMBAT_EARTHDAMAGE: { + color = TEXTCOLOR_LIGHTGREEN; + effect = CONST_ME_GREEN_RINGS; + break; + } + + case COMBAT_FIREDAMAGE: { + color = TEXTCOLOR_ORANGE; + effect = CONST_ME_HITBYFIRE; + break; + } + + case COMBAT_LIFEDRAIN: { + color = TEXTCOLOR_RED; + effect = CONST_ME_MAGIC_RED; + break; + } + default: { + color = TEXTCOLOR_NONE; + effect = CONST_ME_NONE; + break; + } + } +} + +bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) +{ + const Position& targetPos = target->getPosition(); + if (damage.value > 0) { + if (target->getHealth() <= 0) { + return false; + } + + int32_t realHealthChange = target->getHealth(); + target->gainHealth(attacker, damage.value); + realHealthChange = target->getHealth() - realHealthChange; + + if (realHealthChange > 0 && !target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_MAGIC_BLUE); + } + } else { + if (Monster* monster = target->getMonster()) { + // makes monsters aggressive when damaged + // basically stands for UNDERATTACK stance under CipSoft servers + // the attacker must be valid everytime (avoid field ticks damage to trigger condition) + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && attacker) { + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_AGGRESSIVE, 3000); + monster->addCondition(condition, true); + } + } + + if (!target->isAttackable()) { + if (!target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_POFF); + } + return true; + } + + Player* attackerPlayer; + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } else { + attackerPlayer = nullptr; + } + damage.value = std::abs(damage.value); + + int32_t healthChange = damage.value; + if (healthChange == 0) { + return true; + } + + SpectatorVec list; + if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { + int32_t manaDamage = std::min(target->getMana(), healthChange); + if (manaDamage != 0) { + target->drainMana(attacker, manaDamage); + map.getSpectators(list, targetPos, true, true); + addMagicEffect(list, targetPos, CONST_ME_LOSEENERGY); + + std::string damageString = std::to_string(manaDamage); + + Player* targetPlayer = target->getPlayer(); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; + } else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; + } else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + } + + damage.value -= manaDamage; + if (damage.value < 0) { + damage.value = 0; + } + } + } + + int32_t realDamage = damage.value; + if (realDamage == 0) { + return true; + } + + int32_t targetHealth = target->getHealth(); + if (damage.value >= targetHealth) { + damage.value = targetHealth; + } + + realDamage = damage.value; + if (realDamage == 0) { + return true; + } else if (realDamage >= targetHealth) { + for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { + if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { + return false; + } + } + } + + target->drainHealth(attacker, realDamage); + if (list.empty()) { + map.getSpectators(list, targetPos, true, true); + } + addCreatureHealth(list, target); + + TextColor_t color = TEXTCOLOR_NONE; + uint8_t hitEffect; + if (damage.value) { + combatGetTypeInfo(damage.type, target, color, hitEffect); + if (hitEffect != CONST_ME_NONE) { + addMagicEffect(list, targetPos, hitEffect); + } + } + + if (color != TEXTCOLOR_NONE) { + std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint"); + + Player* targetPlayer = target->getPlayer(); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << "."; + } else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " due to your own attack."; + } else { + ss << "You lose " << damageString << " due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + std::string realDamageStr = std::to_string(realDamage); + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); + } + } + } + + return true; +} + +bool Game::combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange) +{ + if (manaChange > 0) { + target->changeMana(manaChange); + } else { + const Position& targetPos = target->getPosition(); + if (!target->isAttackable()) { + if (!target->isInGhostMode()) { + addMagicEffect(targetPos, CONST_ME_POFF); + } + return false; + } + + Player* attackerPlayer; + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } else { + attackerPlayer = nullptr; + } + + int32_t manaLoss = std::min(target->getMana(), -manaChange); + BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); + if (blockType != BLOCK_NONE) { + addMagicEffect(targetPos, CONST_ME_POFF); + return false; + } + + if (manaLoss <= 0) { + return true; + } + + target->drainMana(attacker, manaLoss); + + std::string damageString = std::to_string(manaLoss); + + Player* targetPlayer = target->getPlayer(); + + SpectatorVec list; + map.getSpectators(list, targetPos, false, true); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; + } else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; + } else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + } + } + + return true; +} + +void Game::addCreatureHealth(const Creature* target) +{ + SpectatorVec list; + map.getSpectators(list, target->getPosition(), true, true); + addCreatureHealth(list, target); +} + +void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) +{ + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureHealth(target); + } + } +} + +void Game::addMagicEffect(const Position& pos, uint8_t effect) +{ + SpectatorVec list; + map.getSpectators(list, pos, true, true); + addMagicEffect(list, pos, effect); +} + +void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect) +{ + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendMagicEffect(pos, effect); + } + } +} + +void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +{ + SpectatorVec list; + map.getSpectators(list, fromPos, false, true); + map.getSpectators(list, toPos, false, true); + addDistanceEffect(list, fromPos, toPos, effect); +} + +void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect) +{ + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); + } + } +} + +void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + SpectatorVec list; + map.getSpectators(list, pos, false, true); + addAnimatedText(list, pos, color, text); +} + +void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text) +{ + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendAnimatedText(pos, color, text); + } + } +} + +void Game::addMonsterSayText(const Position& pos, const std::string& text) +{ + SpectatorVec list; + map.getSpectators(list, pos, false, true); + + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureSay(nullptr, TALKTYPE_MONSTER_SAY, text, &pos); + } + } +} + +void Game::startDecay(Item* item) +{ + if (!item || !item->canDecay()) { + return; + } + + ItemDecayState_t decayState = item->getDecaying(); + if (decayState == DECAYING_TRUE) { + return; + } + + if (item->getDuration() > 0) { + item->incrementReferenceCounter(); + item->setDecaying(DECAYING_TRUE); + toDecayItems.push_front(item); + } else { + internalDecayItem(item); + } +} + +void Game::internalDecayItem(Item* item) +{ + const ItemType& it = Item::items[item->getID()]; + if (it.decayTo != 0) { + Item* newItem = transformItem(item, it.decayTo); + startDecay(newItem); + } else { + ReturnValue ret = internalRemoveItem(item); + if (ret != RETURNVALUE_NOERROR) { + std::cout << "[Debug - Game::internalDecayItem] internalDecayItem failed, error code: " << static_cast(ret) << ", item id: " << item->getID() << std::endl; + } + } +} + +void Game::checkDecay() +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_DECAYINTERVAL, std::bind(&Game::checkDecay, this))); + + size_t bucket = (lastBucket + 1) % EVENT_DECAY_BUCKETS; + + auto it = decayItems[bucket].begin(), end = decayItems[bucket].end(); + while (it != end) { + Item* item = *it; + if (!item->canDecay()) { + item->setDecaying(DECAYING_FALSE); + ReleaseItem(item); + it = decayItems[bucket].erase(it); + continue; + } + + int32_t duration = item->getDuration(); + int32_t decreaseTime = std::min(EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS, duration); + + duration -= decreaseTime; + item->decreaseDuration(decreaseTime); + + if (duration <= 0) { + it = decayItems[bucket].erase(it); + internalDecayItem(item); + ReleaseItem(item); + } else if (duration < EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + it = decayItems[bucket].erase(it); + size_t newBucket = (bucket + ((duration + EVENT_DECAYINTERVAL / 2) / 1000)) % EVENT_DECAY_BUCKETS; + if (newBucket == bucket) { + internalDecayItem(item); + ReleaseItem(item); + } else { + decayItems[newBucket].push_back(item); + } + } else { + ++it; + } + } + + lastBucket = bucket; + cleanup(); +} + +void Game::checkLight() +{ + g_scheduler.addEvent(createSchedulerTask(EVENT_LIGHTINTERVAL, std::bind(&Game::checkLight, this))); + + lightHour += lightHourDelta; + + if (lightHour > 1440) { + lightHour -= 1440; + } + + if (std::abs(lightHour - SUNRISE) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNRISE; + } else if (std::abs(lightHour - SUNSET) < 2 * lightHourDelta) { + lightState = LIGHT_STATE_SUNSET; + } + + int32_t newLightLevel = lightLevel; + bool lightChange = false; + + switch (lightState) { + case LIGHT_STATE_SUNRISE: { + newLightLevel += (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + case LIGHT_STATE_SUNSET: { + newLightLevel -= (LIGHT_LEVEL_DAY - LIGHT_LEVEL_NIGHT) / 30; + lightChange = true; + break; + } + default: + break; + } + + if (newLightLevel <= LIGHT_LEVEL_NIGHT) { + lightLevel = LIGHT_LEVEL_NIGHT; + lightState = LIGHT_STATE_NIGHT; + } else if (newLightLevel >= LIGHT_LEVEL_DAY) { + lightLevel = LIGHT_LEVEL_DAY; + lightState = LIGHT_STATE_DAY; + } else { + lightLevel = newLightLevel; + } + + if (lightChange) { + LightInfo lightInfo; + getWorldLightInfo(lightInfo); + + for (const auto& it : players) { + it.second->sendWorldLight(lightInfo); + } + } +} + +void Game::getWorldLightInfo(LightInfo& lightInfo) const +{ + lightInfo.level = lightLevel; + lightInfo.color = 0xD7; +} + +void Game::addCommandTag(char tag) +{ + for (char commandTag : commandTags) { + if (commandTag == tag) { + return; + } + } + commandTags.push_back(tag); +} + +void Game::resetCommandTag() +{ + commandTags.clear(); +} + +void Game::shutdown() +{ + std::cout << "Shutting down..." << std::flush; + + g_scheduler.shutdown(); + g_databaseTasks.shutdown(); + g_dispatcher.shutdown(); + map.spawns.clear(); + raids.clear(); + + cleanup(); + + if (serviceManager) { + serviceManager->stop(); + } + + ConnectionManager::getInstance().closeAll(); + + std::cout << " done!" << std::endl; +} + +void Game::cleanup() +{ + //free memory + for (auto creature : ToReleaseCreatures) { + creature->decrementReferenceCounter(); + } + ToReleaseCreatures.clear(); + + for (auto item : ToReleaseItems) { + item->decrementReferenceCounter(); + } + ToReleaseItems.clear(); + + for (Item* item : toDecayItems) { + const uint32_t dur = item->getDuration(); + if (dur >= EVENT_DECAYINTERVAL * EVENT_DECAY_BUCKETS) { + decayItems[lastBucket].push_back(item); + } else { + decayItems[(lastBucket + 1 + dur / 1000) % EVENT_DECAY_BUCKETS].push_back(item); + } + } + toDecayItems.clear(); +} + +void Game::ReleaseCreature(Creature* creature) +{ + ToReleaseCreatures.push_back(creature); +} + +void Game::ReleaseItem(Item* item) +{ + ToReleaseItems.push_back(item); +} + +void Game::broadcastMessage(const std::string& text, MessageClasses type) const +{ + std::cout << "> Broadcasted message: \"" << text << "\"." << std::endl; + for (const auto& it : players) { + it.second->sendTextMessage(type, text); + } +} + +void Game::updateCreatureSkull(const Creature* creature) +{ + if (getWorldType() != WORLD_TYPE_PVP) { + return; + } + + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureSkull(creature); + } +} + +void Game::updatePlayerShield(Player* player) +{ + SpectatorVec list; + map.getSpectators(list, player->getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->sendCreatureShield(player); + } +} + +void Game::updatePremium(Account& account) +{ + bool save = false; + time_t timeNow = time(nullptr); + + if (account.premiumDays != 0 && account.premiumDays != std::numeric_limits::max()) { + if (account.lastDay == 0) { + account.lastDay = timeNow; + save = true; + } else { + uint32_t days = (timeNow - account.lastDay) / 86400; + if (days > 0) { + if (days >= account.premiumDays) { + account.premiumDays = 0; + account.lastDay = 0; + } else { + account.premiumDays -= days; + time_t remainder = (timeNow - account.lastDay) % 86400; + account.lastDay = timeNow - remainder; + } + + save = true; + } + } + } else if (account.lastDay != 0) { + account.lastDay = 0; + save = true; + } + + if (save && !IOLoginData::saveAccount(account)) { + std::cout << "> ERROR: Failed to save account: " << account.id << "!" << std::endl; + } +} + +void Game::loadMotdNum() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); + if (result) { + motdNum = result->getNumber("value"); + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); + } + + result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); + if (result) { + motdHash = result->getString("value"); + if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { + ++motdNum; + } + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); + } +} + +void Game::saveMotdNum() const +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; + db->executeQuery(query.str()); + + query.str(std::string()); + query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; + db->executeQuery(query.str()); +} + +void Game::checkPlayersRecord() +{ + const size_t playersOnline = getPlayersOnline(); + if (playersOnline > playersRecord) { + uint32_t previousRecord = playersRecord; + playersRecord = playersOnline; + + for (const auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { + it.second->executeRecord(playersRecord, previousRecord); + } + updatePlayersRecord(); + } +} + +void Game::updatePlayersRecord() const +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; + db->executeQuery(query.str()); +} + +void Game::loadPlayersRecord() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); + if (result) { + playersRecord = result->getNumber("value"); + } else { + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); + } +} + +uint64_t Game::getExperienceStage(uint32_t level) +{ + if (!stagesEnabled) { + return g_config.getNumber(ConfigManager::RATE_EXPERIENCE); + } + + if (useLastStageLevel && level >= lastStageLevel) { + return stages[lastStageLevel]; + } + + return stages[level]; +} + +bool Game::loadExperienceStages() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/stages.xml"); + if (!result) { + printXMLError("Error - Game::loadExperienceStages", "data/XML/stages.xml", result); + return false; + } + + for (auto stageNode : doc.child("stages").children()) { + if (strcasecmp(stageNode.name(), "config") == 0) { + stagesEnabled = stageNode.attribute("enabled").as_bool(); + } else { + uint32_t minLevel, maxLevel, multiplier; + + pugi::xml_attribute minLevelAttribute = stageNode.attribute("minlevel"); + if (minLevelAttribute) { + minLevel = pugi::cast(minLevelAttribute.value()); + } else { + minLevel = 1; + } + + pugi::xml_attribute maxLevelAttribute = stageNode.attribute("maxlevel"); + if (maxLevelAttribute) { + maxLevel = pugi::cast(maxLevelAttribute.value()); + } else { + maxLevel = 0; + lastStageLevel = minLevel; + useLastStageLevel = true; + } + + pugi::xml_attribute multiplierAttribute = stageNode.attribute("multiplier"); + if (multiplierAttribute) { + multiplier = pugi::cast(multiplierAttribute.value()); + } else { + multiplier = 1; + } + + if (useLastStageLevel) { + stages[lastStageLevel] = multiplier; + } else { + for (uint32_t i = minLevel; i <= maxLevel; ++i) { + stages[i] = multiplier; + } + } + } + } + return true; +} + +void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + if (!invitedPlayer || invitedPlayer->isInviting(player)) { + return; + } + + if (invitedPlayer->getParty()) { + std::ostringstream ss; + ss << invitedPlayer->getName() << " is already in a party."; + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return; + } + + Party* party = player->getParty(); + if (!party) { + party = new Party(player); + } else if (party->getLeader() != player) { + return; + } + + party->invitePlayer(*invitedPlayer); +} + +void Game::playerJoinParty(uint32_t playerId, uint32_t leaderId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* leader = getPlayerByID(leaderId); + if (!leader || !leader->isInviting(player)) { + return; + } + + Party* party = leader->getParty(); + if (!party || party->getLeader() != leader) { + return; + } + + if (player->getParty()) { + player->sendTextMessage(MESSAGE_INFO_DESCR, "You are already in a party."); + return; + } + + party->joinParty(*player); +} + +void Game::playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || party->getLeader() != player) { + return; + } + + Player* invitedPlayer = getPlayerByID(invitedId); + if (!invitedPlayer || !player->isInviting(invitedPlayer)) { + return; + } + + party->revokeInvitation(*invitedPlayer); +} + +void Game::playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || party->getLeader() != player) { + return; + } + + Player* newLeader = getPlayerByID(newLeaderId); + if (!newLeader || !player->isPartner(newLeader)) { + return; + } + + party->passPartyLeadership(newLeader); +} + +void Game::playerLeaveParty(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return; + } + + party->leaveParty(player); +} + +void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Party* party = player->getParty(); + if (!party || player->hasCondition(CONDITION_INFIGHT)) { + return; + } + + party->setSharedExperience(player, sharedExpActive); +} + +void Game::playerProcessRuleViolationReport(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + return; + } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + auto it = ruleViolations.find(reporter->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + if (!ruleViolation.pending) { + return; + } + + ruleViolation.gamemasterId = player->getID(); + ruleViolation.pending = false; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendRemoveRuleViolationReport(reporter->getName()); + } + } + } +} + +void Game::playerCloseRuleViolationReport(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + closeRuleViolationReport(reporter); +} + +void Game::playerCancelRuleViolationReport(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + cancelRuleViolationReport(player); +} + +void Game::playerReportRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it != ruleViolations.end()) { + player->sendCancelMessage("You already have a pending rule violation report. Close it before starting a new one."); + return; + } + + RuleViolation ruleViolation = RuleViolation(player->getID(), text); + ruleViolations[player->getID()] = ruleViolation; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendToChannel(player, TALKTYPE_RVR_CHANNEL, text, CHANNEL_RULE_REP); + } + } + } +} + +void Game::playerContinueRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& rvr = it->second; + Player* toPlayer = getPlayerByID(rvr.gamemasterId); + if (!toPlayer) { + return; + } + + toPlayer->sendCreatureSay(player, TALKTYPE_RVR_CONTINUE, text, 0); + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Message sent to Counsellor."); +} + +void Game::kickPlayer(uint32_t playerId, bool displayEffect) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->kickPlayer(displayEffect); +} + +void Game::playerReportBug(uint32_t playerId, const std::string& message) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (player->getAccountType() == ACCOUNT_TYPE_NORMAL) { + return; + } + + std::string fileName = "data/reports/" + player->getName() + " report.txt"; + FILE* file = fopen(fileName.c_str(), "a"); + if (!file) { + player->sendTextMessage(MESSAGE_EVENT_DEFAULT, "There was an error when processing your report, please contact a gamemaster."); + return; + } + + const Position& playerPosition = player->getPosition(); + fprintf(file, "------------------------------\nName: %s [Player Position: %u, %u, %u]\nComment: %s\n", player->getName().c_str(), playerPosition.x, playerPosition.y, playerPosition.z, message.c_str()); + fclose(file); + + player->sendTextMessage(MESSAGE_EVENT_DEFAULT, "Your report has been sent to " + g_config.getString(ConfigManager::SERVER_NAME) + "."); +} + +void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + // TODO: move debug assertions to database + FILE* file = fopen("client_assertions.txt", "a"); + if (file) { + fprintf(file, "----- %s - %s (%s) -----\n", formatDate(time(nullptr)).c_str(), player->getName().c_str(), convertIPToString(player->getIP()).c_str()); + fprintf(file, "%s\n%s\n%s\n%s\n", assertLine.c_str(), date.c_str(), description.c_str(), comment.c_str()); + fclose(file); + } +} + +void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + for (CreatureEvent* creatureEvent : player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE)) { + creatureEvent->executeExtendedOpcode(player, opcode, buffer); + } +} + +void Game::closeRuleViolationReport(Player* player) +{ + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + ruleViolations.erase(it); + player->sendLockRuleViolationReport(); + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); + } + } + } +} + +void Game::cancelRuleViolationReport(Player* player) +{ + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + Player* gamemaster = getPlayerByID(ruleViolation.gamemasterId); + if (!ruleViolation.pending && gamemaster) { + // Send to the responder + gamemaster->sendRuleViolationCancel(player->getName()); + } + + // Send to channel + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); + } + } + } + + // Erase it + ruleViolations.erase(it); +} + +void Game::forceAddCondition(uint32_t creatureId, Condition* condition) +{ + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + delete condition; + return; + } + + creature->addCondition(condition, true); +} + +void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) +{ + Creature* creature = getCreatureByID(creatureId); + if (!creature) { + return; + } + + creature->removeCondition(type, true); +} + +void Game::addPlayer(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames[lowercase_name] = player; + wildcardTree.insert(lowercase_name); + players[player->getID()] = player; +} + +void Game::removePlayer(Player* player) +{ + const std::string& lowercase_name = asLowerCaseString(player->getName()); + mappedPlayerNames.erase(lowercase_name); + wildcardTree.remove(lowercase_name); + players.erase(player->getID()); +} + +void Game::addNpc(Npc* npc) +{ + npcs[npc->getID()] = npc; +} + +void Game::removeNpc(Npc* npc) +{ + npcs.erase(npc->getID()); +} + +void Game::addMonster(Monster* monster) +{ + monsters[monster->getID()] = monster; +} + +void Game::removeMonster(Monster* monster) +{ + monsters.erase(monster->getID()); +} + +Guild* Game::getGuild(uint32_t id) const +{ + auto it = guilds.find(id); + if (it == guilds.end()) { + return nullptr; + } + return it->second; +} + +void Game::addGuild(Guild* guild) +{ + guilds[guild->getId()] = guild; +} + +void Game::removeGuild(uint32_t guildId) +{ + guilds.erase(guildId); +} + +void Game::internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable) +{ + if (stackable) { + for (Item* item : itemList) { + if (item->getItemCount() > amount) { + internalRemoveItem(item, amount); + break; + } else { + amount -= item->getItemCount(); + internalRemoveItem(item); + } + } + } else { + for (Item* item : itemList) { + internalRemoveItem(item); + } + } +} + +BedItem* Game::getBedBySleeper(uint32_t guid) const +{ + auto it = bedSleepersMap.find(guid); + if (it == bedSleepersMap.end()) { + return nullptr; + } + return it->second; +} + +void Game::setBedSleeper(BedItem* bed, uint32_t guid) +{ + bedSleepersMap[guid] = bed; +} + +void Game::removeBedSleeper(uint32_t guid) +{ + auto it = bedSleepersMap.find(guid); + if (it != bedSleepersMap.end()) { + bedSleepersMap.erase(it); + } +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..20d80e2 --- /dev/null +++ b/src/game.h @@ -0,0 +1,564 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F +#define FS_GAME_H_3EC96D67DD024E6093B3BAC29B7A6D7F + +#include "account.h" +#include "combat.h" +#include "commands.h" +#include "groups.h" +#include "map.h" +#include "position.h" +#include "item.h" +#include "container.h" +#include "player.h" +#include "raids.h" +#include "npc.h" +#include "wildcardtree.h" + +class ServiceManager; +class Creature; +class Monster; +class Npc; +class CombatInfo; + +enum stackPosType_t { + STACKPOS_MOVE, + STACKPOS_LOOK, + STACKPOS_TOPDOWN_ITEM, + STACKPOS_USEITEM, + STACKPOS_USETARGET, +}; + +enum WorldType_t { + WORLD_TYPE_NO_PVP = 1, + WORLD_TYPE_PVP = 2, + WORLD_TYPE_PVP_ENFORCED = 3, +}; + +enum GameState_t { + GAME_STATE_STARTUP, + GAME_STATE_INIT, + GAME_STATE_NORMAL, + GAME_STATE_CLOSED, + GAME_STATE_SHUTDOWN, + GAME_STATE_CLOSING, + GAME_STATE_MAINTAIN, +}; + +enum LightState_t { + LIGHT_STATE_DAY, + LIGHT_STATE_NIGHT, + LIGHT_STATE_SUNSET, + LIGHT_STATE_SUNRISE, +}; + +struct RuleViolation { + RuleViolation() = default; + RuleViolation(uint32_t _reporterId, const std::string& _text) : + reporterId(_reporterId), + gamemasterId(0), + text(_text), + pending(true) + { + } + + uint32_t reporterId; + uint32_t gamemasterId; + std::string text; + bool pending; +}; + +static constexpr int32_t EVENT_LIGHTINTERVAL = 10000; +static constexpr int32_t EVENT_DECAYINTERVAL = 250; +static constexpr int32_t EVENT_DECAY_BUCKETS = 4; + +/** + * Main Game class. + * This class is responsible to control everything that happens + */ + +class Game +{ + public: + Game() = default; + ~Game(); + + // non-copyable + Game(const Game&) = delete; + Game& operator=(const Game&) = delete; + + void start(ServiceManager* manager); + + void forceAddCondition(uint32_t creatureId, Condition* condition); + void forceRemoveCondition(uint32_t creatureId, ConditionType_t type); + + bool loadMainMap(const std::string& filename); + void loadMap(const std::string& path); + + /** + * Get the map size - info purpose only + * \param width width of the map + * \param height height of the map + */ + void getMapDimensions(uint32_t& width, uint32_t& height) const { + width = map.width; + height = map.height; + } + + void setWorldType(WorldType_t type); + WorldType_t getWorldType() const { + return worldType; + } + + Cylinder* internalGetCylinder(Player* player, const Position& pos) const; + Thing* internalGetThing(Player* player, const Position& pos, int32_t index, + uint32_t spriteId, stackPosType_t type) const; + static void internalGetPosition(Item* item, Position& pos, uint8_t& stackpos); + + static std::string getTradeErrorDescription(ReturnValue ret, Item* item); + + /** + * Returns a creature based on the unique creature identifier + * \param id is the unique creature id to get a creature pointer to + * \returns A Creature pointer to the creature + */ + Creature* getCreatureByID(uint32_t id); + + /** + * Returns a monster based on the unique creature identifier + * \param id is the unique monster id to get a monster pointer to + * \returns A Monster pointer to the monster + */ + Monster* getMonsterByID(uint32_t id); + + /** + * Returns a npc based on the unique creature identifier + * \param id is the unique npc id to get a npc pointer to + * \returns A NPC pointer to the npc + */ + Npc* getNpcByID(uint32_t id); + + /** + * Returns a player based on the unique creature identifier + * \param id is the unique player id to get a player pointer to + * \returns A Pointer to the player + */ + Player* getPlayerByID(uint32_t id); + + /** + * Returns a creature based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the creature + */ + Creature* getCreatureByName(const std::string& s); + + /** + * Returns a npc based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the npc + */ + Npc* getNpcByName(const std::string& s); + + /** + * Returns a player based on a string name identifier + * \param s is the name identifier + * \returns A Pointer to the player + */ + Player* getPlayerByName(const std::string& s); + + /** + * Returns a player based on guid + * \returns A Pointer to the player + */ + Player* getPlayerByGUID(const uint32_t& guid); + + /** + * Returns a player based on a string name identifier, with support for the "~" wildcard. + * \param s is the name identifier, with or without wildcard + * \param player will point to the found player (if any) + * \return "RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE" or "RETURNVALUE_NAMEISTOOAMBIGIOUS" + */ + ReturnValue getPlayerByNameWildcard(const std::string& s, Player*& player); + + /** + * Returns a player based on an account number identifier + * \param acc is the account identifier + * \returns A Pointer to the player + */ + Player* getPlayerByAccount(uint32_t acc); + + /* Place Creature on the map without sending out events to the surrounding. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forced If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool internalPlaceCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); + + /** + * Place Creature on the map. + * \param creature Creature to place on the map + * \param pos The position to place the creature + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param force If true, placing the creature will not fail because of obstacles (creatures/items) + */ + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool force = false); + + /** + * Remove Creature from the map. + * Removes the Creature the map + * \param c Creature to remove + */ + bool removeCreature(Creature* creature, bool isLogout = true); + + void addCreatureCheck(Creature* creature); + static void removeCreatureCheck(Creature* creature); + + size_t getPlayersOnline() const { + return players.size(); + } + size_t getMonstersOnline() const { + return monsters.size(); + } + size_t getNpcsOnline() const { + return npcs.size(); + } + uint32_t getPlayersRecord() const { + return playersRecord; + } + + void getWorldLightInfo(LightInfo& lightInfo) const; + + ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); + ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); + + ReturnValue internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, + Item* item, uint32_t count, Item** _moveItem, uint32_t flags = 0, Creature* actor = nullptr, Item* tradeItem = nullptr); + + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index = INDEX_WHEREEVER, + uint32_t flags = 0, bool test = false); + ReturnValue internalAddItem(Cylinder* toCylinder, Item* item, int32_t index, + uint32_t flags, bool test, uint32_t& remainderCount); + ReturnValue internalRemoveItem(Item* item, int32_t count = -1, bool test = false, uint32_t flags = 0); + + ReturnValue internalPlayerAddItem(Player* player, Item* item, bool dropOnMap = true, slots_t slot = CONST_SLOT_WHEREEVER); + + /** + * Find an item of a certain type + * \param cylinder to search the item + * \param itemId is the item to remove + * \param subType is the extra type an item can have such as charges/fluidtype, default is -1 + * meaning it's not used + * \param depthSearch if true it will check child containers aswell + * \returns A pointer to the item to an item and nullptr if not found + */ + Item* findItemOfType(Cylinder* cylinder, uint16_t itemId, + bool depthSearch = true, int32_t subType = -1) const; + + /** + * Remove/Add item(s) with a monetary value + * \param cylinder to remove the money from + * \param money is the amount to remove + * \param flags optional flags to modifiy the default behaviour + * \returns true if the removal was successful + */ + bool removeMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Add item(s) with monetary value + * \param cylinder which will receive money + * \param money the amount to give + * \param flags optional flags to modify default behavior + */ + void addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags = 0); + + /** + * Transform one item to another type/count + * \param item is the item to transform + * \param newId is the new itemid + * \param newCount is the new count value, use default value (-1) to not change it + * \returns true if the tranformation was successful + */ + Item* transformItem(Item* item, uint16_t newId, int32_t newCount = -1); + + /** + * Teleports an object to another position + * \param thing is the object to teleport + * \param newPos is the new position + * \param pushMove force teleport if false + * \param flags optional flags to modify default behavior + * \returns true if the teleportation was successful + */ + ReturnValue internalTeleport(Thing* thing, const Position& newPos, bool pushMove = true, uint32_t flags = 0); + + /** + * Turn a creature to a different direction. + * \param creature Creature to change the direction + * \param dir Direction to turn to + */ + bool internalCreatureTurn(Creature* creature, Direction dir); + + /** + * Creature wants to say something. + * \param creature Creature pointer + * \param type Type of message + * \param text The text to say + */ + bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, + bool ghostMode, SpectatorVec* listPtr = nullptr, const Position* pos = nullptr); + + void loadPlayersRecord(); + void checkPlayersRecord(); + + void kickPlayer(uint32_t playerId, bool displayEffect); + void playerReportBug(uint32_t playerId, const std::string& message); + void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment); + + bool internalStartTrade(Player* player, Player* partner, Item* tradeItem); + void internalCloseTrade(Player* player); + bool playerBroadcastMessage(Player* player, const std::string& text) const; + void broadcastMessage(const std::string& text, MessageClasses type) const; + + //Implementation of player invoked events + void playerMoveThing(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, + const Position& toPos, uint8_t count); + void playerMoveCreatureByID(uint32_t playerId, uint32_t movingCreatureId, const Position& movingCreatureOrigPos, const Position& toPos); + void playerMoveCreature(Player* playerId, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile); + void playerMoveItemByPlayerID(uint32_t playerId, const Position& fromPos, uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count); + void playerMoveItem(Player* player, const Position& fromPos, + uint16_t spriteId, uint8_t fromStackPos, const Position& toPos, uint8_t count, Item* item, Cylinder* toCylinder); + void playerMove(uint32_t playerId, Direction direction); + void playerCreatePrivateChannel(uint32_t playerId); + void playerChannelInvite(uint32_t playerId, const std::string& name); + void playerChannelExclude(uint32_t playerId, const std::string& name); + void playerRequestChannels(uint32_t playerId); + void playerOpenChannel(uint32_t playerId, uint16_t channelId); + void playerCloseChannel(uint32_t playerId, uint16_t channelId); + void playerOpenPrivateChannel(uint32_t playerId, std::string& receiver); + void playerReceivePing(uint32_t playerId); + void playerReceivePingBack(uint32_t playerId); + void playerAutoWalk(uint32_t playerId, const std::forward_list& listDir); + void playerStopAutoWalk(uint32_t playerId); + void playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, + uint16_t fromSpriteId, const Position& toPos, uint8_t toStackPos, uint16_t toSpriteId); + void playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPos, uint8_t index, uint16_t spriteId); + void playerUseWithCreature(uint32_t playerId, const Position& fromPos, uint8_t fromStackPos, uint32_t creatureId, uint16_t spriteId); + void playerCloseContainer(uint32_t playerId, uint8_t cid); + void playerMoveUpContainer(uint32_t playerId, uint8_t cid); + void playerUpdateContainer(uint32_t playerId, uint8_t cid); + void playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stackPos, const uint16_t spriteId); + void playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::string& text); + void playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index); + void playerUpdateHouseWindow(uint32_t playerId, uint8_t listId, uint32_t windowTextId, const std::string& text); + void playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t stackPos, + uint32_t tradePlayerId, uint16_t spriteId); + void playerAcceptTrade(uint32_t playerId); + void playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t index); + void playerCloseTrade(uint32_t playerId); + void playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId); + void playerFollowCreature(uint32_t playerId, uint32_t creatureId); + void playerCancelAttackAndFollow(uint32_t playerId); + void playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode); + void playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos); + void playerLookInBattleList(uint32_t playerId, uint32_t creatureId); + void playerRequestAddVip(uint32_t playerId, const std::string& name); + void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); + void playerTurn(uint32_t playerId, Direction dir); + void playerRequestOutfit(uint32_t playerId); + void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, + const std::string& receiver, const std::string& text); + void playerChangeOutfit(uint32_t playerId, Outfit_t outfit); + void playerInviteToParty(uint32_t playerId, uint32_t invitedId); + void playerJoinParty(uint32_t playerId, uint32_t leaderId); + void playerRevokePartyInvitation(uint32_t playerId, uint32_t invitedId); + void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); + void playerLeaveParty(uint32_t playerId); + void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); + void playerProcessRuleViolationReport(uint32_t playerId, const std::string& name); + void playerCloseRuleViolationReport(uint32_t playerId, const std::string& name); + void playerCancelRuleViolationReport(uint32_t playerId); + void playerReportRuleViolationReport(Player* player, const std::string& text); + void playerContinueRuleViolationReport(Player* player, const std::string& text); + void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer); + + void closeRuleViolationReport(Player* player); + void cancelRuleViolationReport(Player* player); + + static void updatePremium(Account& account); + + void cleanup(); + void shutdown(); + void ReleaseCreature(Creature* creature); + void ReleaseItem(Item* item); + + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; + bool isSightClear(const Position& fromPos, const Position& toPos, bool sameFloor) const; + + void changeSpeed(Creature* creature, int32_t varSpeedDelta); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit); + void internalCreatureChangeVisible(Creature* creature, bool visible); + void changeLight(const Creature* creature); + void updateCreatureSkull(const Creature* player); + void updatePlayerShield(Player* player); + + GameState_t getGameState() const; + void setGameState(GameState_t newState); + void saveGameState(); + + //Events + void checkCreatureWalk(uint32_t creatureId); + void updateCreatureWalk(uint32_t creatureId); + void checkCreatureAttack(uint32_t creatureId); + void checkCreatures(size_t index); + void checkLight(); + + bool combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field); + + void combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect); + + bool combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage); + bool combatChangeMana(Creature* attacker, Creature* target, int32_t manaChange); + + //animation help functions + void addCreatureHealth(const Creature* target); + static void addCreatureHealth(const SpectatorVec& list, const Creature* target); + void addMagicEffect(const Position& pos, uint8_t effect); + static void addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect); + void addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect); + static void addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect); + void addAnimatedText(const Position& pos, uint8_t color, const std::string& text); + static void addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text); + void addMonsterSayText(const Position& pos, const std::string& text); + + void addCommandTag(char tag); + void resetCommandTag(); + + void startDecay(Item* item); + int32_t getLightHour() const { + return lightHour; + } + + bool loadExperienceStages(); + uint64_t getExperienceStage(uint32_t level); + + void loadMotdNum(); + void saveMotdNum() const; + const std::string& getMotdHash() const { return motdHash; } + uint32_t getMotdNum() const { return motdNum; } + void incrementMotdNum() { motdNum++; } + + const std::unordered_map& getRuleViolationReports() const { return ruleViolations; } + const std::unordered_map& getPlayers() const { return players; } + const std::map& getNpcs() const { return npcs; } + + void addPlayer(Player* player); + void removePlayer(Player* player); + + void addNpc(Npc* npc); + void removeNpc(Npc* npc); + + void addMonster(Monster* npc); + void removeMonster(Monster* npc); + + Guild* getGuild(uint32_t id) const; + void addGuild(Guild* guild); + void removeGuild(uint32_t guildId); + + void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); + + BedItem* getBedBySleeper(uint32_t guid) const; + void setBedSleeper(BedItem* bed, uint32_t guid); + void removeBedSleeper(uint32_t guid); + + Groups groups; + Map map; + Raids raids; + + protected: + bool playerSayCommand(Player* player, const std::string& text); + bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); + void playerWhisper(Player* player, const std::string& text); + bool playerYell(Player* player, const std::string& text); + bool playerSpeakTo(Player* player, SpeakClasses type, const std::string& receiver, const std::string& text); + + void checkDecay(); + void internalDecayItem(Item* item); + + //list of reported rule violations, for correct channel listing + std::unordered_map ruleViolations; + + std::unordered_map players; + std::unordered_map mappedPlayerNames; + std::unordered_map guilds; + std::map stages; + + std::list decayItems[EVENT_DECAY_BUCKETS]; + std::list checkCreatureLists[EVENT_CREATURECOUNT]; + + std::forward_list toDecayItems; + + std::vector ToReleaseCreatures; + std::vector ToReleaseItems; + std::vector commandTags; + + size_t lastBucket = 0; + + WildcardTreeNode wildcardTree { false }; + + std::map npcs; + std::map monsters; + + //list of items that are in trading state, mapped to the player + std::map tradeItems; + + std::map bedSleepersMap; + + Commands commands; + + static constexpr int32_t LIGHT_LEVEL_DAY = 250; + static constexpr int32_t LIGHT_LEVEL_NIGHT = 40; + static constexpr int32_t SUNSET = 1305; + static constexpr int32_t SUNRISE = 430; + + GameState_t gameState = GAME_STATE_NORMAL; + WorldType_t worldType = WORLD_TYPE_PVP; + + LightState_t lightState = LIGHT_STATE_DAY; + uint8_t lightLevel = LIGHT_LEVEL_DAY; + int32_t lightHour = SUNRISE + (SUNSET - SUNRISE) / 2; + // (1440 minutes/day)/(3600 seconds/day)*10 seconds event interval + int32_t lightHourDelta = 1400 * 10 / 3600; + + ServiceManager* serviceManager = nullptr; + + void updatePlayersRecord() const; + uint32_t playersRecord = 0; + + std::string motdHash; + uint32_t motdNum = 0; + + uint32_t lastStageLevel = 0; + bool stagesEnabled = false; + bool useLastStageLevel = false; +}; + +#endif diff --git a/src/globalevent.cpp b/src/globalevent.cpp new file mode 100644 index 0000000..53463a8 --- /dev/null +++ b/src/globalevent.cpp @@ -0,0 +1,337 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "globalevent.h" +#include "tools.h" +#include "scheduler.h" +#include "pugicast.h" + +extern ConfigManager g_config; + +GlobalEvents::GlobalEvents() : + scriptInterface("GlobalEvent Interface") +{ + scriptInterface.initState(); +} + +GlobalEvents::~GlobalEvents() +{ + clear(); +} + +void GlobalEvents::clearMap(GlobalEventMap& map) +{ + for (const auto& it : map) { + delete it.second; + } + map.clear(); +} + +void GlobalEvents::clear() +{ + g_scheduler.stopEvent(thinkEventId); + thinkEventId = 0; + g_scheduler.stopEvent(timerEventId); + timerEventId = 0; + + clearMap(thinkMap); + clearMap(serverMap); + clearMap(timerMap); + + scriptInterface.reInitState(); +} + +Event* GlobalEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "globalevent") != 0) { + return nullptr; + } + return new GlobalEvent(&scriptInterface); +} + +bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&) +{ + GlobalEvent* globalEvent = static_cast(event); //event is guaranteed to be a GlobalEvent + if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { + auto result = timerMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + if (timerEventId == 0) { + timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this))); + } + return true; + } + } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) { + auto result = serverMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + return true; + } + } else { // think event + auto result = thinkMap.emplace(globalEvent->getName(), globalEvent); + if (result.second) { + if (thinkEventId == 0) { + thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this))); + } + return true; + } + } + + std::cout << "[Warning - GlobalEvents::configureEvent] Duplicate registered globalevent with name: " << globalEvent->getName() << std::endl; + return false; +} + +void GlobalEvents::startup() const +{ + execute(GLOBALEVENT_STARTUP); +} + +void GlobalEvents::timer() +{ + time_t now = time(nullptr); + + int64_t nextScheduledTime = std::numeric_limits::max(); + + auto it = timerMap.begin(); + while (it != timerMap.end()) { + GlobalEvent* globalEvent = it->second; + + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + if (nextExecutionTime > 0) { + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + ++it; + continue; + } + + if (!globalEvent->executeEvent()) { + it = timerMap.erase(it); + continue; + } + + nextExecutionTime = 86400; + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + + ++it; + } + + if (nextScheduledTime != std::numeric_limits::max()) { + timerEventId = g_scheduler.addEvent(createSchedulerTask(std::max(1000, nextScheduledTime * 1000), + std::bind(&GlobalEvents::timer, this))); + } +} + +void GlobalEvents::think() +{ + int64_t now = OTSYS_TIME(); + + int64_t nextScheduledTime = std::numeric_limits::max(); + for (const auto& it : thinkMap) { + GlobalEvent* globalEvent = it.second; + + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + if (nextExecutionTime > 0) { + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + continue; + } + + if (!globalEvent->executeEvent()) { + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl; + } + + nextExecutionTime = globalEvent->getInterval(); + if (nextExecutionTime < nextScheduledTime) { + nextScheduledTime = nextExecutionTime; + } + + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + } + + if (nextScheduledTime != std::numeric_limits::max()) { + thinkEventId = g_scheduler.addEvent(createSchedulerTask(nextScheduledTime, std::bind(&GlobalEvents::think, this))); + } +} + +void GlobalEvents::execute(GlobalEvent_t type) const +{ + for (const auto& it : serverMap) { + GlobalEvent* globalEvent = it.second; + if (globalEvent->getEventType() == type) { + globalEvent->executeEvent(); + } + } +} + +GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) +{ + switch (type) { + case GLOBALEVENT_NONE: return thinkMap; + case GLOBALEVENT_TIMER: return timerMap; + case GLOBALEVENT_STARTUP: + case GLOBALEVENT_SHUTDOWN: + case GLOBALEVENT_RECORD: { + GlobalEventMap retMap; + for (const auto& it : serverMap) { + if (it.second->getEventType() == type) { + retMap[it.first] = it.second; + } + } + return retMap; + } + default: return GlobalEventMap(); + } +} + +GlobalEvent::GlobalEvent(LuaScriptInterface* interface) : Event(interface) {} + +bool GlobalEvent::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - GlobalEvent::configureEvent] Missing name for a globalevent" << std::endl; + return false; + } + + name = nameAttribute.as_string(); + eventType = GLOBALEVENT_NONE; + + pugi::xml_attribute attr; + if ((attr = node.attribute("time"))) { + std::vector params = vectorAtoi(explodeString(attr.as_string(), ":")); + + int32_t hour = params.front(); + if (hour < 0 || hour > 23) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + + interval |= hour << 16; + + int32_t min = 0; + int32_t sec = 0; + if (params.size() > 1) { + min = params[1]; + if (min < 0 || min > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid minute \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + + if (params.size() > 2) { + sec = params[2]; + if (sec < 0 || sec > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << attr.as_string() << "\" for globalevent with name: " << name << std::endl; + return false; + } + } + } + + time_t current_time = time(nullptr); + tm* timeinfo = localtime(¤t_time); + timeinfo->tm_hour = hour; + timeinfo->tm_min = min; + timeinfo->tm_sec = sec; + + time_t difference = static_cast(difftime(mktime(timeinfo), current_time)); + if (difference < 0) { + difference += 86400; + } + + nextExecution = current_time + difference; + eventType = GLOBALEVENT_TIMER; + } else if ((attr = node.attribute("type"))) { + const char* value = attr.value(); + if (strcasecmp(value, "startup") == 0) { + eventType = GLOBALEVENT_STARTUP; + } else if (strcasecmp(value, "shutdown") == 0) { + eventType = GLOBALEVENT_SHUTDOWN; + } else if (strcasecmp(value, "record") == 0) { + eventType = GLOBALEVENT_RECORD; + } else { + std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl; + return false; + } + } else if ((attr = node.attribute("interval"))) { + interval = std::max(SCHEDULER_MINTICKS, pugi::cast(attr.value())); + nextExecution = OTSYS_TIME() + interval; + } else { + std::cout << "[Error - GlobalEvent::configureEvent] No interval for globalevent with name " << name << std::endl; + return false; + } + return true; +} + +std::string GlobalEvent::getScriptEventName() const +{ + switch (eventType) { + case GLOBALEVENT_STARTUP: return "onStartup"; + case GLOBALEVENT_SHUTDOWN: return "onShutdown"; + case GLOBALEVENT_RECORD: return "onRecord"; + case GLOBALEVENT_TIMER: return "onTime"; + default: return "onThink"; + } +} + +bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) +{ + //onRecord(current, old) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - GlobalEvent::executeRecord] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + lua_pushnumber(L, current); + lua_pushnumber(L, old); + return scriptInterface->callFunction(2); +} + +bool GlobalEvent::executeEvent() +{ + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(scriptId); + + int32_t params = 0; + if (eventType == GLOBALEVENT_NONE || eventType == GLOBALEVENT_TIMER) { + lua_pushnumber(L, interval); + params = 1; + } + + return scriptInterface->callFunction(params); +} diff --git a/src/globalevent.h b/src/globalevent.h new file mode 100644 index 0000000..1232ddf --- /dev/null +++ b/src/globalevent.h @@ -0,0 +1,114 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C +#define FS_GLOBALEVENT_H_B3FB9B848EA3474B9AFC326873947E3C +#include "baseevents.h" + +#include "const.h" + +enum GlobalEvent_t { + GLOBALEVENT_NONE, + GLOBALEVENT_TIMER, + + GLOBALEVENT_STARTUP, + GLOBALEVENT_SHUTDOWN, + GLOBALEVENT_RECORD, +}; + +class GlobalEvent; +typedef std::map GlobalEventMap; + +class GlobalEvents final : public BaseEvents +{ + public: + GlobalEvents(); + ~GlobalEvents(); + + // non-copyable + GlobalEvents(const GlobalEvents&) = delete; + GlobalEvents& operator=(const GlobalEvents&) = delete; + + void startup() const; + + void timer(); + void think(); + void execute(GlobalEvent_t type) const; + + GlobalEventMap getEventMap(GlobalEvent_t type); + static void clearMap(GlobalEventMap& map); + + protected: + std::string getScriptBaseName() const final { + return "globalevents"; + } + void clear() final; + + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + LuaScriptInterface& getScriptInterface() final { + return scriptInterface; + } + LuaScriptInterface scriptInterface; + + GlobalEventMap thinkMap, serverMap, timerMap; + int32_t thinkEventId = 0, timerEventId = 0; +}; + +class GlobalEvent final : public Event +{ + public: + explicit GlobalEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) final; + + bool executeRecord(uint32_t current, uint32_t old); + bool executeEvent(); + + GlobalEvent_t getEventType() const { + return eventType; + } + + const std::string& getName() const { + return name; + } + + uint32_t getInterval() const { + return interval; + } + + int64_t getNextExecution() const { + return nextExecution; + } + void setNextExecution(int64_t time) { + nextExecution = time; + } + + protected: + GlobalEvent_t eventType = GLOBALEVENT_NONE; + + std::string getScriptEventName() const final; + + std::string name; + int64_t nextExecution = 0; + uint32_t interval = 0; +}; + +#endif diff --git a/src/groups.cpp b/src/groups.cpp new file mode 100644 index 0000000..f56956c --- /dev/null +++ b/src/groups.cpp @@ -0,0 +1,57 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "groups.h" + +#include "pugicast.h" +#include "tools.h" + +bool Groups::load() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/groups.xml"); + if (!result) { + printXMLError("Error - Groups::load", "data/XML/groups.xml", result); + return false; + } + + for (auto groupNode : doc.child("groups").children()) { + Group group; + group.id = pugi::cast(groupNode.attribute("id").value()); + group.name = groupNode.attribute("name").as_string(); + group.flags = pugi::cast(groupNode.attribute("flags").value()); + group.access = groupNode.attribute("access").as_bool(); + group.maxDepotItems = pugi::cast(groupNode.attribute("maxdepotitems").value()); + group.maxVipEntries = pugi::cast(groupNode.attribute("maxvipentries").value()); + groups.push_back(group); + } + return true; +} + +Group* Groups::getGroup(uint16_t id) +{ + for (Group& group : groups) { + if (group.id == id) { + return &group; + } + } + return nullptr; +} diff --git a/src/groups.h b/src/groups.h new file mode 100644 index 0000000..2215678 --- /dev/null +++ b/src/groups.h @@ -0,0 +1,41 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 +#define FS_GROUPS_H_EE39438337D148E1983FB79D936DD8F3 + +struct Group { + std::string name; + uint64_t flags; + uint32_t maxDepotItems; + uint32_t maxVipEntries; + uint16_t id; + bool access; +}; + +class Groups { + public: + bool load(); + Group* getGroup(uint16_t id); + + private: + std::vector groups; +}; + +#endif diff --git a/src/guild.cpp b/src/guild.cpp new file mode 100644 index 0000000..34db634 --- /dev/null +++ b/src/guild.cpp @@ -0,0 +1,66 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "guild.h" + +#include "game.h" + +extern Game g_game; + +void Guild::addMember(Player* player) +{ + membersOnline.push_back(player); +} + +void Guild::removeMember(Player* player) +{ + membersOnline.remove(player); + + if (membersOnline.empty()) { + g_game.removeGuild(id); + delete this; + } +} + +GuildRank* Guild::getRankById(uint32_t rankId) +{ + for (auto& rank : ranks) { + if (rank.id == rankId) { + return &rank; + } + } + return nullptr; +} + +const GuildRank* Guild::getRankByLevel(uint8_t level) const +{ + for (const auto& rank : ranks) { + if (rank.level == level) { + return &rank; + } + } + return nullptr; +} + +void Guild::addRank(uint32_t rankId, const std::string& rankName, uint8_t level) +{ + ranks.emplace_back(rankId, rankName, level); +} diff --git a/src/guild.h b/src/guild.h new file mode 100644 index 0000000..1aeefe9 --- /dev/null +++ b/src/guild.h @@ -0,0 +1,70 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC +#define FS_GUILD_H_C00F0A1D732E4BA88FF62ACBE74D76BC + +class Player; + +struct GuildRank { + uint32_t id; + std::string name; + uint8_t level; + + GuildRank(uint32_t id, std::string name, uint8_t level) : + id(id), name(std::move(name)), level(level) {} +}; + +class Guild +{ + public: + Guild(uint32_t id, std::string name) : name(std::move(name)), id(id) {} + + void addMember(Player* player); + void removeMember(Player* player); + + uint32_t getId() const { + return id; + } + const std::string& getName() const { + return name; + } + const std::list& getMembersOnline() const { + return membersOnline; + } + uint32_t getMemberCount() const { + return memberCount; + } + void setMemberCount(uint32_t count) { + memberCount = count; + } + + GuildRank* getRankById(uint32_t id); + const GuildRank* getRankByLevel(uint8_t level) const; + void addRank(uint32_t id, const std::string& name, uint8_t level); + + private: + std::list membersOnline; + std::vector ranks; + std::string name; + uint32_t id; + uint32_t memberCount = 0; +}; + +#endif diff --git a/src/house.cpp b/src/house.cpp new file mode 100644 index 0000000..88614f9 --- /dev/null +++ b/src/house.cpp @@ -0,0 +1,730 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "pugicast.h" + +#include "house.h" +#include "iologindata.h" +#include "game.h" +#include "configmanager.h" +#include "bed.h" + +extern ConfigManager g_config; +extern Game g_game; + +House::House(uint32_t houseId) : id(houseId) {} + +void House::addTile(HouseTile* tile) +{ + tile->setFlag(TILESTATE_PROTECTIONZONE); + houseTiles.push_back(tile); +} + +void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/) +{ + if (updateDatabase && owner != guid) { + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `houses` SET `owner` = " << guid << ", `bid` = 0, `bid_end` = 0, `last_bid` = 0, `highest_bidder` = 0 WHERE `id` = " << id; + db->executeQuery(query.str()); + } + + if (isLoaded && owner == guid) { + return; + } + + isLoaded = true; + + if (owner != 0) { + //send items to depot + if (player) { + transferToDepot(player); + } else { + transferToDepot(); + } + + for (HouseTile* tile : houseTiles) { + if (const CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + kickPlayer(nullptr, (*creatures)[i]->getPlayer()); + } + } + } + + // Remove players from beds + for (BedItem* bed : bedsList) { + if (bed->getSleeper() != 0) { + bed->wakeUp(nullptr); + } + } + + //clean access lists + owner = 0; + setAccessList(SUBOWNER_LIST, ""); + setAccessList(GUEST_LIST, ""); + + for (Door* door : doorList) { + door->setAccessList(""); + } + + //reset paid date + paidUntil = 0; + rentWarnings = 0; + } + + if (guid != 0) { + std::string name = IOLoginData::getNameByGuid(guid); + if (!name.empty()) { + owner = guid; + ownerName = name; + } + } + + updateDoorDescription(); +} + +void House::updateDoorDescription() const +{ + std::ostringstream ss; + if (owner != 0) { + 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 : doorList) { + it->setSpecialDescription(ss.str()); + } +} + +AccessHouseLevel_t House::getHouseAccessLevel(const Player* player) +{ + if (!player) { + return HOUSE_OWNER; + } + + if (player->hasFlag(PlayerFlag_CanEditHouses)) { + return HOUSE_OWNER; + } + + if (player->getGUID() == owner) { + return HOUSE_OWNER; + } + + if (subOwnerList.isInList(player)) { + return HOUSE_SUBOWNER; + } + + if (guestList.isInList(player)) { + return HOUSE_GUEST; + } + + return HOUSE_NOT_INVITED; +} + +bool House::kickPlayer(Player* player, Player* target) +{ + if (!target) { + return false; + } + + HouseTile* houseTile = dynamic_cast(target->getTile()); + if (!houseTile || houseTile->getHouse() != this) { + return false; + } + + if (getHouseAccessLevel(player) < getHouseAccessLevel(target) || target->hasFlag(PlayerFlag_CanEditHouses)) { + return false; + } + + Position oldPosition = target->getPosition(); + if (g_game.internalTeleport(target, getEntryPosition()) == RETURNVALUE_NOERROR) { + g_game.addMagicEffect(oldPosition, CONST_ME_POFF); + g_game.addMagicEffect(getEntryPosition(), CONST_ME_TELEPORT); + } + return true; +} + +void House::setAccessList(uint32_t listId, const std::string& textlist) +{ + if (listId == GUEST_LIST) { + guestList.parseList(textlist); + } else if (listId == SUBOWNER_LIST) { + subOwnerList.parseList(textlist); + } else { + Door* door = getDoorByNumber(listId); + if (door) { + door->setAccessList(textlist); + } + + // We dont have kick anyone + return; + } + + //kick uninvited players + for (HouseTile* tile : houseTiles) { + if (CreatureVector* creatures = tile->getCreatures()) { + for (int32_t i = creatures->size(); --i >= 0;) { + Player* player = (*creatures)[i]->getPlayer(); + if (player && !isInvited(player)) { + kickPlayer(nullptr, player); + } + } + } + } +} + +bool House::transferToDepot() const +{ + if (townId == 0 || owner == 0) { + return false; + } + + Player* player = g_game.getPlayerByGUID(owner); + if (player) { + transferToDepot(player); + } else { + Player tmpPlayer(nullptr); + if (!IOLoginData::loadPlayerById(&tmpPlayer, owner)) { + return false; + } + + transferToDepot(&tmpPlayer); + IOLoginData::savePlayer(&tmpPlayer); + } + + return true; +} + +bool House::transferToDepot(Player* player) const +{ + if (townId == 0 || owner == 0) { + return false; + } + + ItemList moveItemList; + for (HouseTile* tile : houseTiles) { + if (const TileItemVector* items = tile->getItemList()) { + for (Item* item : *items) { + if (item->isPickupable()) { + moveItemList.push_back(item); + } else { + Container* container = item->getContainer(); + if (container) { + for (Item* containerItem : container->getItemList()) { + moveItemList.push_back(containerItem); + } + } + } + } + } + } + + for (Item* item : moveItemList) { + g_game.internalMoveItem(item->getParent(), player->getDepotLocker(getTownId(), true), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + } + + return true; +} + +bool House::getAccessList(uint32_t listId, std::string& list) const +{ + if (listId == GUEST_LIST) { + guestList.getList(list); + return true; + } else if (listId == SUBOWNER_LIST) { + subOwnerList.getList(list); + return true; + } + + Door* door = getDoorByNumber(listId); + if (!door) { + return false; + } + + return door->getAccessList(list); +} + +bool House::isInvited(const Player* player) +{ + return getHouseAccessLevel(player) != HOUSE_NOT_INVITED; +} + +void House::addDoor(Door* door) +{ + door->incrementReferenceCounter(); + doorList.push_back(door); + door->setHouse(this); + updateDoorDescription(); +} + +void House::removeDoor(Door* door) +{ + auto it = std::find(doorList.begin(), doorList.end(), door); + if (it != doorList.end()) { + door->decrementReferenceCounter(); + doorList.erase(it); + } +} + +void House::addBed(BedItem* bed) +{ + bedsList.push_back(bed); + bed->setHouse(this); +} + +Door* House::getDoorByNumber(uint32_t doorId) const +{ + for (Door* door : doorList) { + if (door->getDoorId() == doorId) { + return door; + } + } + return nullptr; +} + +Door* House::getDoorByPosition(const Position& pos) +{ + for (Door* door : doorList) { + if (door->getPosition() == pos) { + return door; + } + } + return nullptr; +} + +bool House::canEditAccessList(uint32_t listId, const Player* player) +{ + switch (getHouseAccessLevel(player)) { + case HOUSE_OWNER: + return true; + + case HOUSE_SUBOWNER: + return listId == GUEST_LIST; + + default: + return false; + } +} + +HouseTransferItem* House::getTransferItem() +{ + if (transferItem != nullptr) { + return nullptr; + } + + transfer_container.setParent(nullptr); + transferItem = HouseTransferItem::createHouseTransferItem(this); + transfer_container.addThing(transferItem); + return transferItem; +} + +void House::resetTransferItem() +{ + if (transferItem) { + Item* tmpItem = transferItem; + transferItem = nullptr; + transfer_container.setParent(nullptr); + + transfer_container.removeThing(tmpItem, tmpItem->getItemCount()); + g_game.ReleaseItem(tmpItem); + } +} + +HouseTransferItem* HouseTransferItem::createHouseTransferItem(House* house) +{ + HouseTransferItem* transferItem = new HouseTransferItem(house); + transferItem->incrementReferenceCounter(); + transferItem->setID(ITEM_DOCUMENT_RO); + transferItem->setSubType(1); + std::ostringstream ss; + ss << "It is a house transfer document for '" << house->getName() << "'."; + transferItem->setSpecialDescription(ss.str()); + return transferItem; +} + +void HouseTransferItem::onTradeEvent(TradeEvents_t event, Player* owner) +{ + if (event == ON_TRADE_TRANSFER) { + if (house) { + house->executeTransfer(this, owner); + } + + g_game.internalRemoveItem(this, 1); + } else if (event == ON_TRADE_CANCEL) { + if (house) { + house->resetTransferItem(); + } + } +} + +bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) +{ + if (transferItem != item) { + return false; + } + + setOwner(newOwner->getGUID()); + transferItem = nullptr; + return true; +} + +void AccessList::parseList(const std::string& list) +{ + playerList.clear(); + guildList.clear(); + expressionList.clear(); + regExList.clear(); + this->list = list; + if (list.empty()) { + return; + } + + std::istringstream listStream(list); + std::string line; + + while (getline(listStream, line)) { + trimString(line); + trim_left(line, '\t'); + trim_right(line, '\t'); + trimString(line); + + if (line.empty() || line.front() == '#' || line.length() > 100) { + continue; + } + + toLowerCaseString(line); + + std::string::size_type at_pos = line.find("@"); + if (at_pos != std::string::npos) { + addGuild(line.substr(at_pos + 1)); + } else if (line.find("!") != std::string::npos || line.find("*") != std::string::npos || line.find("?") != std::string::npos) { + addExpression(line); + } else { + addPlayer(line); + } + } +} + +void AccessList::addPlayer(const std::string& name) +{ + Player* player = g_game.getPlayerByName(name); + if (player) { + playerList.insert(player->getGUID()); + } else { + uint32_t guid = IOLoginData::getGuidByName(name); + if (guid != 0) { + playerList.insert(guid); + } + } +} + +void AccessList::addGuild(const std::string& name) +{ + uint32_t guildId = IOGuild::getGuildIdByName(name); + if (guildId != 0) { + guildList.insert(guildId); + } +} + +void AccessList::addExpression(const std::string& expression) +{ + if (std::find(expressionList.begin(), expressionList.end(), expression) != expressionList.end()) { + return; + } + + std::string outExp; + outExp.reserve(expression.length()); + + std::string metachars = ".[{}()\\+|^$"; + for (const char c : expression) { + if (metachars.find(c) != std::string::npos) { + outExp.push_back('\\'); + } + outExp.push_back(c); + } + + replaceString(outExp, "*", ".*"); + replaceString(outExp, "?", ".?"); + + try { + if (!outExp.empty()) { + expressionList.push_back(outExp); + + if (outExp.front() == '!') { + if (outExp.length() > 1) { + regExList.emplace_front(std::regex(outExp.substr(1)), false); + } + } else { + regExList.emplace_back(std::regex(outExp), true); + } + } + } catch (...) {} +} + +bool AccessList::isInList(const Player* player) +{ + std::string name = asLowerCaseString(player->getName()); + std::cmatch what; + + for (const auto& it : regExList) { + if (std::regex_match(name.c_str(), what, it.first)) { + return it.second; + } + } + + auto playerIt = playerList.find(player->getGUID()); + if (playerIt != playerList.end()) { + return true; + } + + const Guild* guild = player->getGuild(); + return guild && guildList.find(guild->getId()) != guildList.end(); +} + +void AccessList::getList(std::string& list) const +{ + list = this->list; +} + +Door::Door(uint16_t type) : Item(type) {} + +Attr_ReadValue Door::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_HOUSEDOORID) { + uint8_t doorId; + if (!propStream.read(doorId)) { + return ATTR_READ_ERROR; + } + + setDoorId(doorId); + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +void Door::setHouse(House* house) +{ + if (this->house != nullptr) { + return; + } + + this->house = house; + + if (!accessList) { + accessList.reset(new AccessList()); + } +} + +bool Door::canUse(const Player* player) +{ + if (!house) { + return true; + } + + if (house->getHouseAccessLevel(player) >= HOUSE_SUBOWNER) { + return true; + } + + return accessList->isInList(player); +} + +void Door::setAccessList(const std::string& textlist) +{ + if (!accessList) { + accessList.reset(new AccessList()); + } + + accessList->parseList(textlist); +} + +bool Door::getAccessList(std::string& list) const +{ + if (!house) { + return false; + } + + accessList->getList(list); + return true; +} + +void Door::onRemoved() +{ + Item::onRemoved(); + + if (house) { + house->removeDoor(this); + } +} + +House* Houses::getHouseByPlayerId(uint32_t playerId) +{ + for (const auto& it : houseMap) { + if (it.second->getOwner() == playerId) { + return it.second; + } + } + return nullptr; +} + +bool Houses::loadHousesXML(const std::string& filename) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Houses::loadHousesXML", filename, result); + return false; + } + + for (auto houseNode : doc.child("houses").children()) { + pugi::xml_attribute houseIdAttribute = houseNode.attribute("houseid"); + if (!houseIdAttribute) { + return false; + } + + int32_t houseId = pugi::cast(houseIdAttribute.value()); + + House* house = getHouse(houseId); + if (!house) { + std::cout << "Error: [Houses::loadHousesXML] Unknown house, id = " << houseId << std::endl; + return false; + } + + house->setName(houseNode.attribute("name").as_string()); + + Position entryPos( + pugi::cast(houseNode.attribute("entryx").value()), + pugi::cast(houseNode.attribute("entryy").value()), + pugi::cast(houseNode.attribute("entryz").value()) + ); + if (entryPos.x == 0 && entryPos.y == 0 && entryPos.z == 0) { + std::cout << "[Warning - Houses::loadHousesXML] House entry not set" + << " - Name: " << house->getName() + << " - House id: " << houseId << std::endl; + } + house->setEntryPos(entryPos); + + house->setRent(pugi::cast(houseNode.attribute("rent").value())); + house->setTownId(pugi::cast(houseNode.attribute("townid").value())); + + house->setOwner(0, false); + } + return true; +} + +void Houses::payHouses(RentPeriod_t rentPeriod) const +{ + if (rentPeriod == RENTPERIOD_NEVER) { + return; + } + + time_t currentTime = time(nullptr); + for (const auto& it : houseMap) { + House* house = it.second; + if (house->getOwner() == 0) { + continue; + } + + const uint32_t rent = house->getRent(); + if (rent == 0 || house->getPaidUntil() > currentTime) { + continue; + } + + const uint32_t ownerId = house->getOwner(); + Town* town = g_game.map.towns.getTown(house->getTownId()); + if (!town) { + continue; + } + + Player player(nullptr); + if (!IOLoginData::loadPlayerById(&player, ownerId)) { + // Player doesn't exist, reset house owner + house->setOwner(0); + continue; + } + + if (g_game.removeMoney(player.getDepotLocker(house->getTownId(), true), house->getRent(), FLAG_NOLIMIT)) { + time_t paidUntil = currentTime; + switch (rentPeriod) { + case RENTPERIOD_DAILY: + paidUntil += 24 * 60 * 60; + break; + case RENTPERIOD_WEEKLY: + paidUntil += 24 * 60 * 60 * 7; + break; + case RENTPERIOD_MONTHLY: + paidUntil += 24 * 60 * 60 * 30; + break; + case RENTPERIOD_YEARLY: + paidUntil += 24 * 60 * 60 * 365; + break; + default: + break; + } + + house->setPaidUntil(paidUntil); + } else { + if (house->getPayRentWarnings() < 7) { + int32_t daysLeft = 7 - house->getPayRentWarnings(); + + Item* letter = Item::CreateItem(ITEM_LETTER_STAMPED); + std::string period; + + switch (rentPeriod) { + case RENTPERIOD_DAILY: + period = "daily"; + break; + + case RENTPERIOD_WEEKLY: + period = "weekly"; + break; + + case RENTPERIOD_MONTHLY: + period = "monthly"; + break; + + case RENTPERIOD_YEARLY: + period = "annual"; + break; + + default: + break; + } + + std::ostringstream ss; + ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house."; + letter->setText(ss.str()); + g_game.internalAddItem(player.getDepotLocker(house->getTownId(), true), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + house->setPayRentWarnings(house->getPayRentWarnings() + 1); + } else { + house->setOwner(0, true, &player); + } + } + + IOLoginData::savePlayer(&player); + } +} diff --git a/src/house.h b/src/house.h new file mode 100644 index 0000000..2b126c3 --- /dev/null +++ b/src/house.h @@ -0,0 +1,315 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 +#define FS_HOUSE_H_EB9732E7771A438F9CD0EFA8CB4C58C4 + +#include + +#include "container.h" +#include "housetile.h" +#include "position.h" + +class House; +class BedItem; +class Player; + +class AccessList +{ + public: + void parseList(const std::string& list); + void addPlayer(const std::string& name); + void addGuild(const std::string& name); + void addExpression(const std::string& expression); + + bool isInList(const Player* player); + + void getList(std::string& list) const; + + private: + std::string list; + std::unordered_set playerList; + std::unordered_set guildList; // TODO: include ranks + std::list expressionList; + std::list> regExList; +}; + +class Door final : public Item +{ + public: + explicit Door(uint16_t type); + + // non-copyable + Door(const Door&) = delete; + Door& operator=(const Door&) = delete; + + Door* getDoor() final { + return this; + } + const Door* getDoor() const final { + return this; + } + + House* getHouse() { + return house; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream&) const final {} + + void setDoorId(uint32_t doorId) { + setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); + } + uint32_t getDoorId() const { + return getIntAttr(ITEM_ATTRIBUTE_DOORID); + } + + bool canUse(const Player* player); + + void setAccessList(const std::string& textlist); + bool getAccessList(std::string& list) const; + + void onRemoved() final; + + protected: + void setHouse(House* house); + + private: + House* house = nullptr; + std::unique_ptr accessList; + friend class House; +}; + +enum AccessList_t { + GUEST_LIST = 0x100, + SUBOWNER_LIST = 0x101, +}; + +enum AccessHouseLevel_t { + HOUSE_NOT_INVITED = 0, + HOUSE_GUEST = 1, + HOUSE_SUBOWNER = 2, + HOUSE_OWNER = 3, +}; + +typedef std::list HouseTileList; +typedef std::list HouseBedItemList; + +class HouseTransferItem final : public Item +{ + public: + static HouseTransferItem* createHouseTransferItem(House* house); + + explicit HouseTransferItem(House* house) : Item(0), house(house) {} + + void onTradeEvent(TradeEvents_t event, Player* owner) final; + bool canTransform() const final { + return false; + } + + protected: + House* house; +}; + +class House +{ + public: + explicit House(uint32_t houseId); + + void addTile(HouseTile* tile); + void updateDoorDescription() const; + + bool canEditAccessList(uint32_t listId, const Player* player); + // listId special values: + // GUEST_LIST guest list + // SUBOWNER_LIST subowner list + void setAccessList(uint32_t listId, const std::string& textlist); + bool getAccessList(uint32_t listId, std::string& list) const; + + bool isInvited(const Player* player); + + AccessHouseLevel_t getHouseAccessLevel(const Player* player); + bool kickPlayer(Player* player, Player* target); + + void setEntryPos(Position pos) { + posEntry = pos; + } + const Position& getEntryPosition() const { + return posEntry; + } + + void setName(std::string houseName) { + this->houseName = houseName; + } + const std::string& getName() const { + return houseName; + } + + void setOwner(uint32_t guid, bool updateDatabase = true, Player* player = nullptr); + uint32_t getOwner() const { + return owner; + } + + void setPaidUntil(time_t paid) { + paidUntil = paid; + } + time_t getPaidUntil() const { + return paidUntil; + } + + void setRent(uint32_t rent) { + this->rent = rent; + } + uint32_t getRent() const { + return rent; + } + + void setPayRentWarnings(uint32_t warnings) { + rentWarnings = warnings; + } + uint32_t getPayRentWarnings() const { + return rentWarnings; + } + + void setTownId(uint32_t townId) { + this->townId = townId; + } + uint32_t getTownId() const { + return townId; + } + + uint32_t getId() const { + return id; + } + + void addDoor(Door* door); + void removeDoor(Door* door); + Door* getDoorByNumber(uint32_t doorId) const; + Door* getDoorByPosition(const Position& pos); + + HouseTransferItem* getTransferItem(); + void resetTransferItem(); + bool executeTransfer(HouseTransferItem* item, Player* player); + + const HouseTileList& getTiles() const { + return houseTiles; + } + + const std::list& getDoors() const { + return doorList; + } + + void addBed(BedItem* bed); + const HouseBedItemList& getBeds() const { + return bedsList; + } + uint32_t getBedCount() { + return static_cast(std::ceil(bedsList.size() / 2.)); //each bed takes 2 sqms of space, ceil is just for bad maps + } + + private: + bool transferToDepot() const; + bool transferToDepot(Player* player) const; + + AccessList guestList; + AccessList subOwnerList; + + Container transfer_container{ITEM_LOCKER1}; + + HouseTileList houseTiles; + std::list doorList; + HouseBedItemList bedsList; + + std::string houseName; + std::string ownerName; + + HouseTransferItem* transferItem = nullptr; + + time_t paidUntil = 0; + + uint32_t id; + uint32_t owner = 0; + uint32_t rentWarnings = 0; + uint32_t rent = 0; + uint32_t townId = 0; + + Position posEntry = {}; + + bool isLoaded = false; +}; + +typedef std::map HouseMap; + +enum RentPeriod_t { + RENTPERIOD_DAILY, + RENTPERIOD_WEEKLY, + RENTPERIOD_MONTHLY, + RENTPERIOD_YEARLY, + RENTPERIOD_NEVER, +}; + +class Houses +{ + public: + Houses() = default; + ~Houses() { + for (const auto& it : houseMap) { + delete it.second; + } + } + + // non-copyable + Houses(const Houses&) = delete; + Houses& operator=(const Houses&) = delete; + + House* addHouse(uint32_t id) { + auto it = houseMap.find(id); + if (it != houseMap.end()) { + return it->second; + } + + House* house = new House(id); + houseMap[id] = house; + return house; + } + + House* getHouse(uint32_t houseId) { + auto it = houseMap.find(houseId); + if (it == houseMap.end()) { + return nullptr; + } + return it->second; + } + + House* getHouseByPlayerId(uint32_t playerId); + + bool loadHousesXML(const std::string& filename); + + void payHouses(RentPeriod_t rentPeriod) const; + + const HouseMap& getHouses() const { + return houseMap; + } + + private: + HouseMap houseMap; +}; + +#endif diff --git a/src/housetile.cpp b/src/housetile.cpp new file mode 100644 index 0000000..d206b48 --- /dev/null +++ b/src/housetile.cpp @@ -0,0 +1,122 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "housetile.h" +#include "house.h" +#include "game.h" + +extern Game g_game; + +HouseTile::HouseTile(int32_t x, int32_t y, int32_t z, House* house) : + DynamicTile(x, y, z), house(house) {} + +void HouseTile::addThing(int32_t index, Thing* thing) +{ + Tile::addThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::internalAddThing(uint32_t index, Thing* thing) +{ + Tile::internalAddThing(index, thing); + + if (!thing->getParent()) { + return; + } + + if (Item* item = thing->getItem()) { + updateHouse(item); + } +} + +void HouseTile::updateHouse(Item* item) +{ + if (item->getParent() != this) { + return; + } + + Door* door = item->getDoor(); + if (door) { + if (door->getDoorId() != 0) { + house->addDoor(door); + } + } else { + BedItem* bed = item->getBed(); + if (bed) { + house->addBed(bed); + } + } +} + +ReturnValue HouseTile::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor/* = nullptr*/) const +{ + if (const Creature* creature = thing.getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + return RETURNVALUE_PLAYERISNOTINVITED; + } + } else { + return RETURNVALUE_NOTPOSSIBLE; + } + } else if (thing.getItem() && actor) { + Player* actorPlayer = actor->getPlayer(); + if (!house->isInvited(actorPlayer)) { + return RETURNVALUE_CANNOTTHROW; + } + } + return Tile::queryAdd(index, thing, count, flags, actor); +} + +Tile* HouseTile::queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) +{ + if (const Creature* creature = thing.getCreature()) { + if (const Player* player = creature->getPlayer()) { + if (!house->isInvited(player)) { + const Position& entryPos = house->getEntryPosition(); + Tile* destTile = g_game.map.getTile(entryPos); + if (!destTile) { + std::cout << "Error: [HouseTile::queryDestination] House entry not correct" + << " - Name: " << house->getName() + << " - House id: " << house->getId() + << " - Tile not found: " << entryPos << std::endl; + + destTile = g_game.map.getTile(player->getTemplePosition()); + if (!destTile) { + destTile = &(Tile::nullptr_tile); + } + } + + index = -1; + *destItem = nullptr; + return destTile; + } + } + } + + return Tile::queryDestination(index, thing, destItem, flags); +} diff --git a/src/housetile.h b/src/housetile.h new file mode 100644 index 0000000..ba55d43 --- /dev/null +++ b/src/housetile.h @@ -0,0 +1,52 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B +#define FS_HOUSETILE_H_57D59BEC1CE741D9B142BFC54634505B + +#include "tile.h" + +class House; + +class HouseTile final : public DynamicTile +{ + public: + HouseTile(int32_t x, int32_t y, int32_t z, House* house); + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(int32_t index, Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + + House* getHouse() { + return house; + } + + private: + void updateHouse(Item* item); + + House* house; +}; + +#endif diff --git a/src/ioguild.cpp b/src/ioguild.cpp new file mode 100644 index 0000000..69675dc --- /dev/null +++ b/src/ioguild.cpp @@ -0,0 +1,57 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "ioguild.h" +#include "database.h" + +uint32_t IOGuild::getGuildIdByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(name); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("id"); +} + +void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList) +{ + std::ostringstream query; + query << "SELECT `guild1`, `guild2` FROM `guild_wars` WHERE (`guild1` = " << guildId << " OR `guild2` = " << guildId << ") AND `ended` = 0 AND `status` = 1"; + + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return; + } + + do { + uint32_t guild1 = result->getNumber("guild1"); + if (guildId != guild1) { + guildWarList.push_back(guild1); + } else { + guildWarList.push_back(result->getNumber("guild2")); + } + } while (result->next()); +} diff --git a/src/ioguild.h b/src/ioguild.h new file mode 100644 index 0000000..d956c77 --- /dev/null +++ b/src/ioguild.h @@ -0,0 +1,32 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF +#define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF + +typedef std::vector GuildWarList; + +class IOGuild +{ + public: + static uint32_t getGuildIdByName(const std::string& name); + static void getWarList(uint32_t guildId, GuildWarList& guildWarList); +}; + +#endif diff --git a/src/iologindata.cpp b/src/iologindata.cpp new file mode 100644 index 0000000..a2e5708 --- /dev/null +++ b/src/iologindata.cpp @@ -0,0 +1,950 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iologindata.h" +#include "configmanager.h" +#include "game.h" + +extern ConfigManager g_config; +extern Game g_game; + +Account IOLoginData::loadAccount(uint32_t accno) +{ + Account account; + + std::ostringstream query; + query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accno; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return account; + } + + account.id = result->getNumber("id"); + account.accountType = static_cast(result->getNumber("type")); + account.premiumDays = result->getNumber("premdays"); + account.lastDay = result->getNumber("lastday"); + return account; +} + +bool IOLoginData::saveAccount(const Account& acc) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = " << acc.premiumDays << ", `lastday` = " << acc.lastDay << " WHERE `id` = " << acc.id; + return Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accountNumber; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (transformToSHA1(password) != result->getString("password")) { + return false; + } + + account.id = result->getNumber("id"); + account.accountType = static_cast(result->getNumber("type")); + account.premiumDays = result->getNumber("premdays"); + account.lastDay = result->getNumber("lastday"); + + query.str(std::string()); + query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id; + result = db->storeQuery(query.str()); + if (result) { + do { + if (result->getNumber("deletion") == 0) { + account.characters.push_back(result->getString("name")); + } + } while (result->next()); + std::sort(account.characters.begin(), account.characters.end()); + } + return true; +} + +uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `password` FROM `accounts` WHERE `id` = " << accountNumber; + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + + if (transformToSHA1(password) != result->getString("password")) { + return 0; + } + + uint32_t accountId = result->getNumber("id"); + + query.str(std::string()); + query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName); + result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + + if (result->getNumber("account_id") != accountId || result->getNumber("deletion") != 0) { + return 0; + } + characterName = result->getString("name"); + return accountId; +} + +AccountType_t IOLoginData::getAccountType(uint32_t accountId) +{ + std::ostringstream query; + query << "SELECT `type` FROM `accounts` WHERE `id` = " << accountId; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return ACCOUNT_TYPE_NORMAL; + } + return static_cast(result->getNumber("type")); +} + +void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `type` = " << static_cast(accountType) << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) +{ + if (g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + return; + } + + std::ostringstream query; + if (login) { + query << "INSERT INTO `players_online` VALUES (" << guid << ')'; + } else { + query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; + } + Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::preloadPlayer(Player* player, const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id`, `account_id`, `group_id`, `deletion`, (SELECT `type` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `account_type`"; + if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + query << ", (SELECT `premdays` FROM `accounts` WHERE `accounts`.`id` = `account_id`) AS `premium_days`"; + } + query << " FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (result->getNumber("deletion") != 0) { + return false; + } + + player->setGUID(result->getNumber("id")); + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + if (!group) { + std::cout << "[Error - IOLoginData::preloadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist." << std::endl; + return false; + } + player->setGroup(group); + player->accountNumber = result->getNumber("account_id"); + player->accountType = static_cast(result->getNumber("account_type")); + if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + player->premiumDays = result->getNumber("premium_days"); + } else { + player->premiumDays = std::numeric_limits::max(); + } + return true; +} + +bool IOLoginData::loadPlayerById(Player* player, uint32_t id) +{ + std::ostringstream query; + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `id` = " << id; + return loadPlayer(player, Database::getInstance()->storeQuery(query.str())); +} + +bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) +{ + Database* db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `id`, `name`, `account_id`, `group_id`, `sex`, `vocation`, `experience`, `level`, `maglevel`, `health`, `healthmax`, `blessings`, `mana`, `manamax`, `manaspent`, `soul`, `lookbody`, `lookfeet`, `lookhead`, `looklegs`, `looktype`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `skill_fist`, `skill_fist_tries`, `skill_club`, `skill_club_tries`, `skill_sword`, `skill_sword_tries`, `skill_axe`, `skill_axe_tries`, `skill_dist`, `skill_dist_tries`, `skill_shielding`, `skill_shielding_tries`, `skill_fishing`, `skill_fishing_tries` FROM `players` WHERE `name` = " << db->escapeString(name); + return loadPlayer(player, db->storeQuery(query.str())); +} + +bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) +{ + if (!result) { + return false; + } + + Database* db = Database::getInstance(); + + uint32_t accno = result->getNumber("account_id"); + Account acc = loadAccount(accno); + + player->setGUID(result->getNumber("id")); + player->name = result->getString("name"); + player->accountNumber = accno; + + player->accountType = acc.accountType; + + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + player->premiumDays = std::numeric_limits::max(); + } else { + player->premiumDays = acc.premiumDays; + } + + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + if (!group) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Group ID " << result->getNumber("group_id") << " which doesn't exist" << std::endl; + return false; + } + player->setGroup(group); + + player->bankBalance = result->getNumber("balance"); + + player->setSex(static_cast(result->getNumber("sex"))); + player->level = std::max(1, result->getNumber("level")); + + uint64_t experience = result->getNumber("experience"); + + uint64_t currExpCount = Player::getExpForLevel(player->level); + uint64_t nextExpCount = Player::getExpForLevel(player->level + 1); + if (experience < currExpCount || experience > nextExpCount) { + experience = currExpCount; + } + + player->experience = experience; + + if (currExpCount < nextExpCount) { + player->levelPercent = Player::getPercentLevel(player->experience - currExpCount, nextExpCount - currExpCount); + } else { + player->levelPercent = 0; + } + + player->soul = result->getNumber("soul"); + player->capacity = std::max(400, result->getNumber("cap")) * 100; + player->blessings = result->getNumber("blessings"); + + unsigned long conditionsSize; + const char* conditions = result->getStream("conditions", conditionsSize); + PropStream propStream; + propStream.init(conditions, conditionsSize); + + Condition* condition = Condition::createCondition(propStream); + while (condition) { + if (condition->unserialize(propStream)) { + player->storedConditionList.push_front(condition); + } else { + delete condition; + } + condition = Condition::createCondition(propStream); + } + + if (!player->setVocation(result->getNumber("vocation"))) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Vocation ID " << result->getNumber("vocation") << " which doesn't exist" << std::endl; + return false; + } + + player->mana = result->getNumber("mana"); + player->manaMax = result->getNumber("manamax"); + player->magLevel = result->getNumber("maglevel"); + + uint64_t nextManaCount = player->vocation->getReqMana(player->magLevel + 1); + uint64_t manaSpent = result->getNumber("manaspent"); + if (manaSpent > nextManaCount) { + manaSpent = 0; + } + + player->manaSpent = manaSpent; + player->magLevelPercent = Player::getPercentLevel(player->manaSpent, nextManaCount); + + player->health = result->getNumber("health"); + player->healthMax = result->getNumber("healthmax"); + + player->defaultOutfit.lookType = result->getNumber("looktype"); + player->defaultOutfit.lookHead = result->getNumber("lookhead"); + player->defaultOutfit.lookBody = result->getNumber("lookbody"); + player->defaultOutfit.lookLegs = result->getNumber("looklegs"); + player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); + player->currentOutfit = player->defaultOutfit; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + player->playerKillerEnd = result->getNumber("skulltime"); + + uint16_t skull = result->getNumber("skull"); + if (skull == SKULL_RED) { + player->skull = SKULL_RED; + } + + if (player->playerKillerEnd == 0) { + player->skull = SKULL_NONE; + } + } + + player->loginPosition.x = result->getNumber("posx"); + player->loginPosition.y = result->getNumber("posy"); + player->loginPosition.z = result->getNumber("posz"); + + player->lastLoginSaved = result->getNumber("lastlogin"); + player->lastLogout = result->getNumber("lastlogout"); + + Town* town = g_game.map.towns.getTown(result->getNumber("town_id")); + if (!town) { + std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber("town_id") << " which doesn't exist" << std::endl; + return false; + } + + player->town = town; + + const Position& loginPos = player->loginPosition; + if (loginPos.x == 0 && loginPos.y == 0 && loginPos.z == 0) { + player->loginPosition = player->getTemplePosition(); + } + + static const std::string skillNames[] = {"skill_fist", "skill_club", "skill_sword", "skill_axe", "skill_dist", "skill_shielding", "skill_fishing"}; + static const std::string skillNameTries[] = {"skill_fist_tries", "skill_club_tries", "skill_sword_tries", "skill_axe_tries", "skill_dist_tries", "skill_shielding_tries", "skill_fishing_tries"}; + static constexpr size_t size = sizeof(skillNames) / sizeof(std::string); + for (uint8_t i = 0; i < size; ++i) { + uint16_t skillLevel = result->getNumber(skillNames[i]); + uint64_t skillTries = result->getNumber(skillNameTries[i]); + uint64_t nextSkillTries = player->vocation->getReqSkillTries(i, skillLevel + 1); + if (skillTries > nextSkillTries) { + skillTries = 0; + } + + player->skills[i].level = skillLevel; + player->skills[i].tries = skillTries; + player->skills[i].percent = Player::getPercentLevel(skillTries, nextSkillTries); + } + + std::ostringstream query; + + query << "SELECT `date` FROM `player_murders` WHERE `player_id` = " << player->getGUID() << " ORDER BY `date` ASC"; + if ((result = db->storeQuery(query.str()))) { + do { + player->murderTimeStamps.push_back(result->getNumber("date")); + } while (result->next()); + } + + query.str(std::string()); + query << "SELECT `guild_id`, `rank_id`, `nick` FROM `guild_membership` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + uint32_t guildId = result->getNumber("guild_id"); + uint32_t playerRankId = result->getNumber("rank_id"); + player->guildNick = result->getString("nick"); + + Guild* guild = g_game.getGuild(guildId); + if (!guild) { + query.str(std::string()); + query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; + if ((result = db->storeQuery(query.str()))) { + guild = new Guild(guildId, result->getString("name")); + g_game.addGuild(guild); + + query.str(std::string()); + query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `guild_id` = " << guildId; + + if ((result = db->storeQuery(query.str()))) { + do { + guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); + } while (result->next()); + } + } + } + + if (guild) { + player->guild = guild; + const GuildRank* rank = guild->getRankById(playerRankId); + if (!rank) { + query.str(std::string()); + query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; + + if ((result = db->storeQuery(query.str()))) { + guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); + } + + rank = guild->getRankById(playerRankId); + if (!rank) { + player->guild = nullptr; + } + } + + player->guildRank = rank; + + IOGuild::getWarList(guildId, player->guildWarList); + + query.str(std::string()); + query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; + if ((result = db->storeQuery(query.str()))) { + guild->setMemberCount(result->getNumber("members")); + } + } + } + + query.str(std::string()); + query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + do { + player->learnedInstantSpellList.emplace_front(result->getString("name")); + } while (result->next()); + } + + //load inventory items + ItemMap itemMap; + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_items` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + if (pid >= 1 && pid <= 10) { + player->internalAddThing(pid, item); + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load depot items + itemMap.clear(); + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_depotitems` WHERE `player_id` = " << player->getGUID() << " ORDER BY `sid` DESC"; + if ((result = db->storeQuery(query.str()))) { + loadItems(itemMap, result); + + for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { + const std::pair& pair = it->second; + Item* item = pair.first; + + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + Container* itemContainer = item->getContainer(); + if (itemContainer) { + DepotLocker* locker = itemContainer->getDepotLocker(); + if (locker) { + DepotLocker* existingLocker = player->getDepotLocker(pid, false); + if (!existingLocker) { + player->depotLockerMap[pid] = locker; + } + } + } + } else { + ItemMap::const_iterator it2 = itemMap.find(pid); + if (it2 == itemMap.end()) { + continue; + } + + Container* container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + } + } + } + } + + //load storage map + query.str(std::string()); + query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + if ((result = db->storeQuery(query.str()))) { + do { + player->addStorageValue(result->getNumber("key"), result->getNumber("value")); + } while (result->next()); + } + + //load vip + query.str(std::string()); + query << "SELECT `player_id` FROM `account_viplist` WHERE `account_id` = " << player->getAccount(); + if ((result = db->storeQuery(query.str()))) { + do { + player->addVIPInternal(result->getNumber("player_id")); + } while (result->next()); + } + + player->updateBaseSpeed(); + player->updateInventoryWeight(); + player->updateItemsLight(true); + return true; +} + +bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream) +{ + std::ostringstream ss; + + typedef std::pair containerBlock; + std::list queue; + + int32_t runningId = 100; + + Database* db = Database::getInstance(); + for (const auto& it : itemList) { + int32_t pid = it.first; + Item* item = it.second; + ++runningId; + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + if (!query_insert.addRow(ss)) { + return false; + } + + if (Container* container = item->getContainer()) { + queue.emplace_back(container, runningId); + } + } + + while (!queue.empty()) { + const containerBlock& cb = queue.front(); + Container* container = cb.first; + int32_t parentId = cb.second; + queue.pop_front(); + + for (Item* item : container->getItemList()) { + ++runningId; + + Container* subContainer = item->getContainer(); + if (subContainer) { + queue.emplace_back(subContainer, runningId); + } + + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); + + ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + if (!query_insert.addRow(ss)) { + return false; + } + } + } + return query_insert.execute(); +} + +bool IOLoginData::savePlayer(Player* player) +{ + if (player->getHealth() <= 0) { + player->changeHealth(1); + } + + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + if (result->getNumber("save") == 0) { + query.str(std::string()); + query << "UPDATE `players` SET `lastlogin` = " << player->lastLoginSaved << ", `lastip` = " << player->lastIP << " WHERE `id` = " << player->getGUID(); + return db->executeQuery(query.str()); + } + + //serialize conditions + PropWriteStream propWriteStream; + for (Condition* condition : player->conditions) { + if (condition->isPersistent()) { + condition->serialize(propWriteStream); + propWriteStream.write(CONDITIONATTR_END); + } + } + + size_t conditionsSize; + const char* conditions = propWriteStream.getStream(conditionsSize); + + //First, an UPDATE query to write the player itself + query.str(std::string()); + query << "UPDATE `players` SET "; + query << "`level` = " << player->level << ','; + query << "`group_id` = " << player->group->id << ','; + query << "`vocation` = " << player->getVocationId() << ','; + query << "`health` = " << player->health << ','; + query << "`healthmax` = " << player->healthMax << ','; + query << "`experience` = " << player->experience << ','; + query << "`lookbody` = " << static_cast(player->defaultOutfit.lookBody) << ','; + query << "`lookfeet` = " << static_cast(player->defaultOutfit.lookFeet) << ','; + query << "`lookhead` = " << static_cast(player->defaultOutfit.lookHead) << ','; + query << "`looklegs` = " << static_cast(player->defaultOutfit.lookLegs) << ','; + query << "`looktype` = " << player->defaultOutfit.lookType << ','; + query << "`maglevel` = " << player->magLevel << ','; + query << "`mana` = " << player->mana << ','; + query << "`manamax` = " << player->manaMax << ','; + query << "`manaspent` = " << player->manaSpent << ','; + query << "`soul` = " << static_cast(player->soul) << ','; + query << "`town_id` = " << player->town->getID() << ','; + + const Position& loginPosition = player->getLoginPosition(); + query << "`posx` = " << loginPosition.getX() << ','; + query << "`posy` = " << loginPosition.getY() << ','; + query << "`posz` = " << loginPosition.getZ() << ','; + + query << "`cap` = " << (player->capacity / 100) << ','; + query << "`sex` = " << player->sex << ','; + + if (player->lastLoginSaved != 0) { + query << "`lastlogin` = " << player->lastLoginSaved << ','; + } + + if (player->lastIP != 0) { + query << "`lastip` = " << player->lastIP << ','; + } + + query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ','; + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + query << "`skulltime` = " << player->getPlayerKillerEnd() << ','; + + Skulls_t skull = SKULL_NONE; + if (player->skull == SKULL_RED) { + skull = SKULL_RED; + } + + query << "`skull` = " << static_cast(skull) << ','; + } + + query << "`lastlogout` = " << player->getLastLogout() << ','; + query << "`balance` = " << player->bankBalance << ','; + + query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ','; + query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ','; + query << "`skill_club` = " << player->skills[SKILL_CLUB].level << ','; + query << "`skill_club_tries` = " << player->skills[SKILL_CLUB].tries << ','; + query << "`skill_sword` = " << player->skills[SKILL_SWORD].level << ','; + query << "`skill_sword_tries` = " << player->skills[SKILL_SWORD].tries << ','; + query << "`skill_axe` = " << player->skills[SKILL_AXE].level << ','; + query << "`skill_axe_tries` = " << player->skills[SKILL_AXE].tries << ','; + query << "`skill_dist` = " << player->skills[SKILL_DISTANCE].level << ','; + query << "`skill_dist_tries` = " << player->skills[SKILL_DISTANCE].tries << ','; + query << "`skill_shielding` = " << player->skills[SKILL_SHIELD].level << ','; + query << "`skill_shielding_tries` = " << player->skills[SKILL_SHIELD].tries << ','; + query << "`skill_fishing` = " << player->skills[SKILL_FISHING].level << ','; + query << "`skill_fishing_tries` = " << player->skills[SKILL_FISHING].tries << ','; + + if (!player->isOffline()) { + query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; + } + query << "`blessings` = " << static_cast(player->blessings); + query << " WHERE `id` = " << player->getGUID(); + + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery(query.str())) { + return false; + } + + // learned spells + query.str(std::string()); + query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); + for (const std::string& spellName : player->learnedInstantSpellList) { + query << player->getGUID() << ',' << db->escapeString(spellName); + if (!spellsQuery.addRow(query)) { + return false; + } + } + + if (!spellsQuery.execute()) { + return false; + } + + query.str(std::string()); + query << "DELETE FROM `player_murders` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert murdersQuery("INSERT INTO `player_murders`(`id`, `player_id`, `date`) VALUES "); + for (time_t timestamp : player->murderTimeStamps) { + query << "NULL," << player->getGUID() << ',' << timestamp; + if (!murdersQuery.addRow(query)) { + return false; + } + } + + if (!murdersQuery.execute()) { + return false; + } + + //item saving + query.str(std::string()); + query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + + ItemBlockList itemList; + for (int32_t slotId = 1; slotId <= 10; ++slotId) { + Item* item = player->inventory[slotId]; + if (item) { + itemList.emplace_back(slotId, item); + } + } + + if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { + return false; + } + + //save depot items + query.str(std::string()); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { + return false; + } + + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + itemList.clear(); + + for (const auto& it : player->depotLockerMap) { + itemList.emplace_back(it.first, it.second); + } + + if (!saveItems(player, itemList, depotQuery, propWriteStream)) { + return false; + } + + query.str(std::string()); + query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + if (!db->executeQuery(query.str())) { + return false; + } + + query.str(std::string()); + + DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); + + for (const auto& it : player->storageMap) { + query << player->getGUID() << ',' << it.first << ',' << it.second; + if (!storageQuery.addRow(query)) { + return false; + } + } + + if (!storageQuery.execute()) { + return false; + } + + //End the transaction + return transaction.commit(); +} + +std::string IOLoginData::getNameByGuid(uint32_t guid) +{ + std::ostringstream query; + query << "SELECT `name` FROM `players` WHERE `id` = " << guid; + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (!result) { + return std::string(); + } + return result->getString("name"); +} + +uint32_t IOLoginData::getGuidByName(const std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("id"); +} + +bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `name`, `id`, `group_id`, `account_id` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + name = result->getString("name"); + guid = result->getNumber("id"); + Group* group = g_game.groups.getGroup(result->getNumber("group_id")); + + uint64_t flags; + if (group) { + flags = group->flags; + } else { + flags = 0; + } + + specialVip = (flags & PlayerFlag_SpecialVIP) != 0; + return true; +} + +bool IOLoginData::formatPlayerName(std::string& name) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name); + + DBResult_ptr result = db->storeQuery(query.str()); + if (!result) { + return false; + } + + name = result->getString("name"); + return true; +} + +void IOLoginData::loadItems(ItemMap& itemMap, DBResult_ptr result) +{ + do { + uint32_t sid = result->getNumber("sid"); + uint32_t pid = result->getNumber("pid"); + uint16_t type = result->getNumber("itemtype"); + uint16_t count = result->getNumber("count"); + + unsigned long attrSize; + const char* attr = result->getStream("attributes", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + Item* item = Item::CreateItem(type, count); + if (item) { + if (!item->unserializeAttr(propStream)) { + std::cout << "WARNING: Serialize error in IOLoginData::loadItems" << std::endl; + } + + std::pair pair(item, pid); + itemMap[sid] = pair; + } + } while (result->next()); +} + +void IOLoginData::increaseBankBalance(uint32_t guid, uint64_t bankBalance) +{ + std::ostringstream query; + query << "UPDATE `players` SET `balance` = `balance` + " << bankBalance << " WHERE `id` = " << guid; + Database::getInstance()->executeQuery(query.str()); +} + +bool IOLoginData::hasBiddedOnHouse(uint32_t guid) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `id` FROM `houses` WHERE `highest_bidder` = " << guid << " LIMIT 1"; + return db->storeQuery(query.str()).get() != nullptr; +} + +std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) +{ + std::forward_list entries; + + std::ostringstream query; + query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name` FROM `account_viplist` WHERE `account_id` = " << accountId; + + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + if (result) { + do { + entries.emplace_front( + result->getNumber("player_id"), + result->getString("name") + ); + } while (result->next()); + } + return entries; +} + +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid) +{ + Database* db = Database::getInstance(); + + std::ostringstream query; + query << "INSERT INTO `account_viplist` (`account_id`, `player_id`) VALUES (" << accountId << ',' << guid << ')'; + db->executeQuery(query.str()); +} + +void IOLoginData::removeVIPEntry(uint32_t accountId, uint32_t guid) +{ + std::ostringstream query; + query << "DELETE FROM `account_viplist` WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::addPremiumDays(uint32_t accountId, int32_t addDays) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = `premdays` + " << addDays << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} + +void IOLoginData::removePremiumDays(uint32_t accountId, int32_t removeDays) +{ + std::ostringstream query; + query << "UPDATE `accounts` SET `premdays` = `premdays` - " << removeDays << " WHERE `id` = " << accountId; + Database::getInstance()->executeQuery(query.str()); +} diff --git a/src/iologindata.h b/src/iologindata.h new file mode 100644 index 0000000..9b58fdf --- /dev/null +++ b/src/iologindata.h @@ -0,0 +1,68 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF +#define FS_IOLOGINDATA_H_28B0440BEC594654AC0F4E1A5E42B2EF + +#include "account.h" +#include "player.h" +#include "database.h" + +typedef std::list> ItemBlockList; + +class IOLoginData +{ + public: + static Account loadAccount(uint32_t accno); + static bool saveAccount(const Account& acc); + + static bool loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account); + static uint32_t gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName); + + static AccountType_t getAccountType(uint32_t accountId); + static void setAccountType(uint32_t accountId, AccountType_t accountType); + static void updateOnlineStatus(uint32_t guid, bool login); + static bool preloadPlayer(Player* player, const std::string& name); + + static bool loadPlayerById(Player* player, uint32_t id); + 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 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 bool hasBiddedOnHouse(uint32_t guid); + + static std::forward_list getVIPEntries(uint32_t accountId); + static void addVIPEntry(uint32_t accountId, uint32_t guid); + static void removeVIPEntry(uint32_t accountId, uint32_t guid); + + static void addPremiumDays(uint32_t accountId, int32_t addDays); + static void removePremiumDays(uint32_t accountId, int32_t removeDays); + + protected: + typedef std::map> ItemMap; + + static void loadItems(ItemMap& itemMap, DBResult_ptr result); + static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream); +}; + +#endif diff --git a/src/iomap.cpp b/src/iomap.cpp new file mode 100644 index 0000000..3f6f426 --- /dev/null +++ b/src/iomap.cpp @@ -0,0 +1,462 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomap.h" + +#include "bed.h" + +/* + OTBM_ROOTV1 + | + |--- OTBM_MAP_DATA + | | + | |--- OTBM_TILE_AREA + | | |--- OTBM_TILE + | | |--- OTBM_TILE_SQUARE (not implemented) + | | |--- OTBM_TILE_REF (not implemented) + | | |--- OTBM_HOUSETILE + | | + | |--- OTBM_SPAWNS (not implemented) + | | |--- OTBM_SPAWN_AREA (not implemented) + | | |--- OTBM_MONSTER (not implemented) + | | + | |--- OTBM_TOWNS + | | |--- OTBM_TOWN + | | + | |--- OTBM_WAYPOINTS + | |--- OTBM_WAYPOINT + | + |--- OTBM_ITEM_DEF (not implemented) +*/ + +Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z) +{ + if (!ground) { + return new StaticTile(x, y, z); + } + + Tile* tile; + if ((item && item->isBlocking()) || ground->isBlocking()) { + tile = new StaticTile(x, y, z); + } else { + tile = new DynamicTile(x, y, z); + } + + tile->internalAddThing(ground); + ground->startDecaying(); + ground = nullptr; + return tile; +} + +bool IOMap::loadMap(Map* map, const std::string& identifier) +{ + int64_t start = OTSYS_TIME(); + + FileLoader f; + if (!f.openFile(identifier.c_str(), "OTBM")) { + std::ostringstream ss; + ss << "Could not open the file " << identifier << '.'; + setLastErrorString(ss.str()); + return false; + } + + uint32_t type; + PropStream propStream; + + NODE root = f.getChildNode(nullptr, type); + if (!f.getProps(root, propStream)) { + setLastErrorString("Could not read root property."); + return false; + } + + OTBM_root_header root_header; + if (!propStream.read(root_header)) { + setLastErrorString("Could not read header."); + return false; + } + + uint32_t headerVersion = root_header.version; + if (headerVersion <= 0) { + //In otbm version 1 the count variable after splashes/fluidcontainers and stackables + //are saved as attributes instead, this solves alot of problems with items + //that is changed (stackable/charges/fluidcontainer/splash) during an update. + setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + return false; + } + + if (headerVersion > 2) { + setLastErrorString("Unknown OTBM version detected."); + return false; + } + + std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl; + map->width = root_header.width; + map->height = root_header.height; + + NODE nodeMap = f.getChildNode(root, type); + if (type != OTBM_MAP_DATA) { + setLastErrorString("Could not read data node."); + return false; + } + + if (!f.getProps(nodeMap, propStream)) { + setLastErrorString("Could not read map data attributes."); + return false; + } + + std::string mapDescription; + std::string tmp; + + uint8_t attribute; + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_DESCRIPTION: + if (!propStream.readString(mapDescription)) { + setLastErrorString("Invalid description tag."); + return false; + } + break; + + case OTBM_ATTR_EXT_SPAWN_FILE: + if (!propStream.readString(tmp)) { + setLastErrorString("Invalid spawn tag."); + return false; + } + + map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1); + map->spawnfile += tmp; + break; + + case OTBM_ATTR_EXT_HOUSE_FILE: + if (!propStream.readString(tmp)) { + setLastErrorString("Invalid house tag."); + return false; + } + + map->housefile = identifier.substr(0, identifier.rfind('/') + 1); + map->housefile += tmp; + break; + + default: + setLastErrorString("Unknown header node."); + return false; + } + } + + NODE nodeMapData = f.getChildNode(nodeMap, type); + while (nodeMapData != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Invalid map node."); + return false; + } + + if (type == OTBM_TILE_AREA) { + if (!f.getProps(nodeMapData, propStream)) { + setLastErrorString("Invalid map node."); + return false; + } + + OTBM_Destination_coords area_coord; + if (!propStream.read(area_coord)) { + setLastErrorString("Invalid map node."); + return false; + } + + uint16_t base_x = area_coord.x; + uint16_t base_y = area_coord.y; + uint16_t z = area_coord.z; + + NODE nodeTile = f.getChildNode(nodeMapData, type); + while (nodeTile != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Could not read node data."); + return false; + } + + if (type != OTBM_TILE && type != OTBM_HOUSETILE) { + setLastErrorString("Unknown tile node."); + return false; + } + + if (!f.getProps(nodeTile, propStream)) { + setLastErrorString("Could not read node data."); + return false; + } + + OTBM_Tile_coords tile_coord; + if (!propStream.read(tile_coord)) { + setLastErrorString("Could not read tile position."); + return false; + } + + uint16_t x = base_x + tile_coord.x; + uint16_t y = base_y + tile_coord.y; + + bool isHouseTile = false; + House* house = nullptr; + Tile* tile = nullptr; + Item* ground_item = nullptr; + uint32_t tileflags = TILESTATE_NONE; + + if (type == OTBM_HOUSETILE) { + uint32_t houseId; + if (!propStream.read(houseId)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; + setLastErrorString(ss.str()); + return false; + } + + house = map->houses.addHouse(houseId); + if (!house) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not create house id: " << houseId; + setLastErrorString(ss.str()); + return false; + } + + tile = new HouseTile(x, y, z, house); + house->addTile(static_cast(tile)); + isHouseTile = true; + } + + //read tile attributes + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_TILE_FLAGS: { + uint32_t flags; + if (!propStream.read(flags)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags."; + setLastErrorString(ss.str()); + return false; + } + + if ((flags & OTBM_TILEFLAG_PROTECTIONZONE) != 0) { + tileflags |= TILESTATE_PROTECTIONZONE; + } else if ((flags & OTBM_TILEFLAG_NOPVPZONE) != 0) { + tileflags |= TILESTATE_NOPVPZONE; + } else if ((flags & OTBM_TILEFLAG_PVPZONE) != 0) { + tileflags |= TILESTATE_PVPZONE; + } + + if ((flags & OTBM_TILEFLAG_REFRESH) != 0) { + tileflags |= TILESTATE_REFRESH; + } + + if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) { + tileflags |= TILESTATE_NOLOGOUT; + } + break; + } + + case OTBM_ATTR_ITEM: { + Item* item = Item::CreateItem(propStream); + if (!item) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (isHouseTile && item->isMoveable()) { + //std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + delete item; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, x, y, z); + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } + } + break; + } + + default: + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute."; + setLastErrorString(ss.str()); + return false; + } + } + + NODE nodeItem = f.getChildNode(nodeTile, type); + while (nodeItem) { + if (type != OTBM_ITEM) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type."; + setLastErrorString(ss.str()); + return false; + } + + PropStream stream; + if (!f.getProps(nodeItem, stream)) { + setLastErrorString("Invalid item node."); + return false; + } + + Item* item = Item::CreateItem(stream); + if (!item) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to create item."; + setLastErrorString(ss.str()); + return false; + } + + if (!item->unserializeItemNode(f, nodeItem, stream)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to load item " << item->getID() << '.'; + setLastErrorString(ss.str()); + delete item; + return false; + } + + if (isHouseTile && item->isMoveable()) { + //std::cout << "[Warning - IOMap::loadMap] Moveable item with ID: " << item->getID() << ", in house: " << house->getId() << ", at position [x: " << x << ", y: " << y << ", z: " << z << "]." << std::endl; + delete item; + } else { + if (item->getItemCount() <= 0) { + item->setItemCount(1); + } + + if (tile) { + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } else if (item->isGroundTile()) { + delete ground_item; + ground_item = item; + } else { + tile = createTile(ground_item, item, x, y, z); + tile->internalAddThing(item); + item->startDecaying(); + item->setLoadedFromMap(true); + } + } + + nodeItem = f.getNextNode(nodeItem, type); + } + + if (!tile) { + tile = createTile(ground_item, nullptr, x, y, z); + } + + tile->setFlag(static_cast(tileflags)); + + map->setTile(x, y, z, tile); + + nodeTile = f.getNextNode(nodeTile, type); + } + } else if (type == OTBM_TOWNS) { + NODE nodeTown = f.getChildNode(nodeMapData, type); + while (nodeTown != NO_NODE) { + if (type != OTBM_TOWN) { + setLastErrorString("Unknown town node."); + return false; + } + + if (!f.getProps(nodeTown, propStream)) { + setLastErrorString("Could not read town data."); + return false; + } + + uint32_t townId; + if (!propStream.read(townId)) { + setLastErrorString("Could not read town id."); + return false; + } + + Town* town = map->towns.getTown(townId); + if (!town) { + town = new Town(townId); + map->towns.addTown(townId, town); + } + + std::string townName; + if (!propStream.readString(townName)) { + setLastErrorString("Could not read town name."); + return false; + } + + town->setName(townName); + + OTBM_Destination_coords town_coords; + if (!propStream.read(town_coords)) { + setLastErrorString("Could not read town coordinates."); + return false; + } + + town->setTemplePos(Position(town_coords.x, town_coords.y, town_coords.z)); + + nodeTown = f.getNextNode(nodeTown, type); + } + } else if (type == OTBM_WAYPOINTS) { + NODE nodeWaypoint = f.getChildNode(nodeMapData, type); + while (nodeWaypoint != NO_NODE) { + if (type != OTBM_WAYPOINT) { + setLastErrorString("Unknown waypoint node."); + return false; + } + + if (!f.getProps(nodeWaypoint, propStream)) { + setLastErrorString("Could not read waypoint data."); + return false; + } + + std::string name; + if (!propStream.readString(name)) { + setLastErrorString("Could not read waypoint name."); + return false; + } + + OTBM_Destination_coords waypoint_coords; + if (!propStream.read(waypoint_coords)) { + setLastErrorString("Could not read waypoint coordinates."); + return false; + } + + map->waypoints[name] = Position(waypoint_coords.x, waypoint_coords.y, waypoint_coords.z); + + nodeWaypoint = f.getNextNode(nodeWaypoint, type); + } + } else { + setLastErrorString("Unknown map node."); + return false; + } + + nodeMapData = f.getNextNode(nodeMapData, type); + } + + std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return true; +} diff --git a/src/iomap.h b/src/iomap.h new file mode 100644 index 0000000..9386141 --- /dev/null +++ b/src/iomap.h @@ -0,0 +1,161 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 +#define FS_IOMAP_H_8085D4B1037A44288494A52FDBB775E4 + +#include "item.h" +#include "map.h" +#include "house.h" +#include "spawn.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +enum OTBM_AttrTypes_t { + OTBM_ATTR_DESCRIPTION = 1, + OTBM_ATTR_EXT_FILE = 2, + OTBM_ATTR_TILE_FLAGS = 3, + OTBM_ATTR_ACTION_ID = 4, + OTBM_ATTR_MOVEMENT_ID = 5, + OTBM_ATTR_TEXT = 6, + OTBM_ATTR_DESC = 7, + OTBM_ATTR_TELE_DEST = 8, + OTBM_ATTR_ITEM = 9, + OTBM_ATTR_DEPOT_ID = 10, + OTBM_ATTR_EXT_SPAWN_FILE = 11, + OTBM_ATTR_RUNE_CHARGES = 12, + OTBM_ATTR_EXT_HOUSE_FILE = 13, + OTBM_ATTR_HOUSEDOORID = 14, + OTBM_ATTR_COUNT = 15, + OTBM_ATTR_DURATION = 16, + OTBM_ATTR_DECAYING_STATE = 17, + OTBM_ATTR_WRITTENDATE = 18, + OTBM_ATTR_WRITTENBY = 19, + OTBM_ATTR_SLEEPERGUID = 20, + OTBM_ATTR_SLEEPSTART = 21, + OTBM_ATTR_CHARGES = 22, + OTBM_ATTR_KEYNUMBER = 23, + OTBM_ATTR_KEYHOLENUMBER = 24, + OTBM_ATTR_DOORQUESTNUMBER = 25, + OTBM_ATTR_DOORQUESTVALUE = 26, + OTBM_ATTR_DOORLEVEL = 27, + OTBM_ATTR_CHESTQUESTNUMBER = 28, +}; + +enum OTBM_NodeTypes_t { + OTBM_ROOTV1 = 1, + OTBM_MAP_DATA = 2, + OTBM_ITEM_DEF = 3, + OTBM_TILE_AREA = 4, + OTBM_TILE = 5, + OTBM_ITEM = 6, + OTBM_TILE_SQUARE = 7, + OTBM_TILE_REF = 8, + OTBM_SPAWNS = 9, + OTBM_SPAWN_AREA = 10, + OTBM_MONSTER = 11, + OTBM_TOWNS = 12, + OTBM_TOWN = 13, + OTBM_HOUSETILE = 14, + OTBM_WAYPOINTS = 15, + OTBM_WAYPOINT = 16, +}; + +enum OTBM_TileFlag_t : uint32_t { + OTBM_TILEFLAG_PROTECTIONZONE = 1 << 0, + OTBM_TILEFLAG_NOPVPZONE = 1 << 2, + OTBM_TILEFLAG_NOLOGOUT = 1 << 3, + OTBM_TILEFLAG_PVPZONE = 1 << 4, + OTBM_TILEFLAG_REFRESH = 1 << 5, +}; + +#pragma pack(1) + +struct OTBM_root_header { + uint32_t version; + uint16_t width; + uint16_t height; + uint32_t majorVersionItems; + uint32_t minorVersionItems; +}; + +struct OTBM_Destination_coords { + uint16_t x; + uint16_t y; + uint8_t z; +}; + +struct OTBM_Tile_coords { + uint8_t x; + uint8_t y; +}; + +#pragma pack() + +class IOMap +{ + static Tile* createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8_t z); + + public: + bool loadMap(Map* map, const std::string& identifier); + + /* Load the spawns + * \param map pointer to the Map class + * \returns Returns true if the spawns were loaded successfully + */ + static bool loadSpawns(Map* map) { + if (map->spawnfile.empty()) { + //OTBM file doesn't tell us about the spawnfile, + //lets guess it is mapname-spawn.xml. + map->spawnfile = g_config.getString(ConfigManager::MAP_NAME); + map->spawnfile += "-spawn.xml"; + } + + return map->spawns.loadFromXml(map->spawnfile); + } + + /* Load the houses (not house tile-data) + * \param map pointer to the Map class + * \returns Returns true if the houses were loaded successfully + */ + static bool loadHouses(Map* map) { + if (map->housefile.empty()) { + //OTBM file doesn't tell us about the housefile, + //lets guess it is mapname-house.xml. + map->housefile = g_config.getString(ConfigManager::MAP_NAME); + map->housefile += "-house.xml"; + } + + return map->houses.loadHousesXML(map->housefile); + } + + const std::string& getLastErrorString() const { + return errorString; + } + + void setLastErrorString(std::string error) { + errorString = error; + } + + protected: + std::string errorString; +}; + +#endif diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp new file mode 100644 index 0000000..0e83463 --- /dev/null +++ b/src/iomapserialize.cpp @@ -0,0 +1,372 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomapserialize.h" +#include "game.h" +#include "bed.h" + +extern Game g_game; + +void IOMapSerialize::loadHouseItems(Map* map) +{ + int64_t start = OTSYS_TIME(); + + DBResult_ptr result = Database::getInstance()->storeQuery("SELECT `data` FROM `tile_store`"); + if (!result) { + return; + } + + do { + unsigned long attrSize; + const char* attr = result->getStream("data", attrSize); + + PropStream propStream; + propStream.init(attr, attrSize); + + uint16_t x, y; + uint8_t z; + if (!propStream.read(x) || !propStream.read(y) || !propStream.read(z)) { + continue; + } + + Tile* tile = map->getTile(x, y, z); + if (!tile) { + continue; + } + + uint32_t item_count; + if (!propStream.read(item_count)) { + continue; + } + + while (item_count--) { + loadItem(propStream, tile); + } + } while (result->next()); + std::cout << "> Loaded house items in: " << (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; +} + +bool IOMapSerialize::saveHouseItems() +{ + int64_t start = OTSYS_TIME(); + Database* db = Database::getInstance(); + std::ostringstream query; + + //Start the transaction + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + //clear old tile data + if (!db->executeQuery("DELETE FROM `tile_store`")) { + return false; + } + + DBInsert stmt("INSERT INTO `tile_store` (`house_id`, `data`) VALUES "); + + PropWriteStream stream; + for (const auto& it : g_game.map.houses.getHouses()) { + //save house items + House* house = it.second; + for (HouseTile* tile : house->getTiles()) { + saveTile(stream, tile); + + size_t attributesSize; + const char* attributes = stream.getStream(attributesSize); + if (attributesSize > 0) { + query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize); + if (!stmt.addRow(query)) { + return false; + } + stream.clear(); + } + } + } + + if (!stmt.execute()) { + return false; + } + + //End the transaction + bool success = transaction.commit(); + std::cout << "> Saved house items in: " << + (OTSYS_TIME() - start) / (1000.) << " s" << std::endl; + return success; +} + +bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) +{ + while (container->serializationCount > 0) { + if (!loadItem(propStream, container)) { + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + return false; + } + container->serializationCount--; + } + + uint8_t endAttr; + if (!propStream.read(endAttr) || endAttr != 0) { + std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error for container item: " << container->getID() << std::endl; + return false; + } + return true; +} + +bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) +{ + uint16_t id; + if (!propStream.read(id)) { + return false; + } + + Tile* tile = nullptr; + if (parent->getParent() == nullptr) { + tile = parent->getTile(); + } + + const ItemType& iType = Item::items[id]; + if (iType.moveable || !tile) { + //create a new item + Item* item = Item::CreateItem(id); + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + if (container && !loadContainer(propStream, container)) { + delete item; + return false; + } + + parent->internalAddThing(item); + item->startDecaying(); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + delete item; + return false; + } + } + } else { + // Stationary items like doors/beds/blackboards/bookcases + Item* item = nullptr; + if (const TileItemVector* items = tile->getItemList()) { + for (Item* findItem : *items) { + if (findItem->getID() == id) { + item = findItem; + break; + } else if (iType.isDoor() && findItem->getDoor()) { + item = findItem; + break; + } else if (iType.isBed() && findItem->getBed()) { + item = findItem; + break; + } + } + } + + if (item) { + if (item->unserializeAttr(propStream)) { + Container* container = item->getContainer(); + if (container && !loadContainer(propStream, container)) { + return false; + } + + g_game.transformItem(item, id); + } else { + std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl; + } + } else { + //The map changed since the last save, just read the attributes + std::unique_ptr dummy(Item::CreateItem(id)); + if (dummy) { + dummy->unserializeAttr(propStream); + Container* container = dummy->getContainer(); + if (container) { + if (!loadContainer(propStream, container)) { + return false; + } + } else if (BedItem* bedItem = dynamic_cast(dummy.get())) { + uint32_t sleeperGUID = bedItem->getSleeper(); + if (sleeperGUID != 0) { + g_game.removeBedSleeper(sleeperGUID); + } + } + } + } + } + return true; +} + +void IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item) +{ + const Container* container = item->getContainer(); + + // Write ID & props + stream.write(item->getID()); + item->serializeAttr(stream); + + if (container) { + // Hack our way into the attributes + stream.write(ATTR_CONTAINER_ITEMS); + stream.write(container->size()); + for (auto it = container->getReversedItems(), end = container->getReversedEnd(); it != end; ++it) { + saveItem(stream, *it); + } + } + + stream.write(0x00); // attr end +} + +void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) +{ + const TileItemVector* tileItems = tile->getItemList(); + if (!tileItems) { + return; + } + + std::forward_list items; + uint16_t count = 0; + for (Item* item : *tileItems) { + const ItemType& it = Item::items[item->getID()]; + + // Note that these are NEGATED, ie. these are the items that will be saved. + if (!(it.moveable || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { + continue; + } + + items.push_front(item); + ++count; + } + + if (!items.empty()) { + const Position& tilePosition = tile->getPosition(); + stream.write(tilePosition.x); + stream.write(tilePosition.y); + stream.write(tilePosition.z); + + stream.write(count); + for (const Item* item : items) { + saveItem(stream, item); + } + } +} + +bool IOMapSerialize::loadHouseInfo() +{ + Database* db = Database::getInstance(); + + DBResult_ptr result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + if (!result) { + return false; + } + + do { + House* house = g_game.map.houses.getHouse(result->getNumber("id")); + if (house) { + house->setOwner(result->getNumber("owner"), false); + house->setPaidUntil(result->getNumber("paid")); + house->setPayRentWarnings(result->getNumber("warnings")); + } + } while (result->next()); + + result = db->storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); + if (result) { + do { + House* house = g_game.map.houses.getHouse(result->getNumber("house_id")); + if (house) { + house->setAccessList(result->getNumber("listid"), result->getString("list")); + } + } while (result->next()); + } + return true; +} + +bool IOMapSerialize::saveHouseInfo() +{ + Database* db = Database::getInstance(); + + DBTransaction transaction; + if (!transaction.begin()) { + return false; + } + + if (!db->executeQuery("DELETE FROM `house_lists`")) { + return false; + } + + std::ostringstream query; + for (const auto& it : g_game.map.houses.getHouses()) { + House* house = it.second; + query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getId(); + DBResult_ptr result = db->storeQuery(query.str()); + if (result) { + query.str(std::string()); + query << "UPDATE `houses` SET `owner` = " << house->getOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getPayRentWarnings() << ", `name` = " << db->escapeString(house->getName()) << ", `town_id` = " << house->getTownId() << ", `rent` = " << house->getRent() << ", `size` = " << house->getTiles().size() << ", `beds` = " << house->getBedCount() << " WHERE `id` = " << house->getId(); + } else { + query.str(std::string()); + query << "INSERT INTO `houses` (`id`, `owner`, `paid`, `warnings`, `name`, `town_id`, `rent`, `size`, `beds`) VALUES (" << house->getId() << ',' << house->getOwner() << ',' << house->getPaidUntil() << ',' << house->getPayRentWarnings() << ',' << db->escapeString(house->getName()) << ',' << house->getTownId() << ',' << house->getRent() << ',' << house->getTiles().size() << ',' << house->getBedCount() << ')'; + } + + db->executeQuery(query.str()); + query.str(std::string()); + } + + DBInsert stmt("INSERT INTO `house_lists` (`house_id` , `listid` , `list`) VALUES "); + + for (const auto& it : g_game.map.houses.getHouses()) { + House* house = it.second; + + std::string listText; + if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { + query << house->getId() << ',' << GUEST_LIST << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + + if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { + query << house->getId() << ',' << SUBOWNER_LIST << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + + for (Door* door : house->getDoors()) { + if (door->getAccessList(listText) && !listText.empty()) { + query << house->getId() << ',' << door->getDoorId() << ',' << db->escapeString(listText); + if (!stmt.addRow(query)) { + return false; + } + + listText.clear(); + } + } + } + + if (!stmt.execute()) { + return false; + } + + return transaction.commit(); +} diff --git a/src/iomapserialize.h b/src/iomapserialize.h new file mode 100644 index 0000000..c7b269b --- /dev/null +++ b/src/iomapserialize.h @@ -0,0 +1,42 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D +#define FS_IOMAPSERIALIZE_H_7E903658F34E44F9BE03A713B55A3D6D + +#include "database.h" +#include "map.h" + +class IOMapSerialize +{ + public: + static void loadHouseItems(Map* map); + static bool saveHouseItems(); + static bool loadHouseInfo(); + static bool saveHouseInfo(); + + protected: + static void saveItem(PropWriteStream& stream, const Item* item); + static void saveTile(PropWriteStream& stream, const Tile* tile); + + static bool loadContainer(PropStream& propStream, Container* container); + static bool loadItem(PropStream& propStream, Cylinder* parent); +}; + +#endif diff --git a/src/item.cpp b/src/item.cpp new file mode 100644 index 0000000..cb69843 --- /dev/null +++ b/src/item.cpp @@ -0,0 +1,1242 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "item.h" +#include "container.h" +#include "teleport.h" +#include "mailbox.h" +#include "house.h" +#include "game.h" +#include "bed.h" + +#include "actions.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Vocations g_vocations; + +Items Item::items; + +Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) +{ + Item* newItem = nullptr; + + const ItemType& it = Item::items[type]; + if (it.group == ITEM_GROUP_DEPRECATED) { + return nullptr; + } + + if (it.stackable && count == 0) { + count = 1; + } + + if (it.id != 0) { + if (it.isDepot()) { + newItem = new DepotLocker(type); + } else if (it.isContainer() || it.isChest()) { + newItem = new Container(type); + } else if (it.isTeleport()) { + newItem = new Teleport(type); + } else if (it.isMagicField()) { + newItem = new MagicField(type); + } else if (it.isDoor()) { + newItem = new Door(type); + } else if (it.isMailbox()) { + newItem = new Mailbox(type); + } else if (it.isBed()) { + newItem = new BedItem(type); + } else { + newItem = new Item(type, count); + } + + newItem->incrementReferenceCounter(); + } + + return newItem; +} + +Container* Item::CreateItemAsContainer(const uint16_t type, uint16_t size) +{ + const ItemType& it = Item::items[type]; + if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || it.isDepot() || it.isSplash() || it.isDoor()) { + return nullptr; + } + + Container* newItem = new Container(type, size); + newItem->incrementReferenceCounter(); + return newItem; +} + +Item* Item::CreateItem(PropStream& propStream) +{ + uint16_t id; + if (!propStream.read(id)) { + return nullptr; + } + + switch (id) { + case ITEM_FIREFIELD_PVP_FULL: + id = ITEM_FIREFIELD_PERSISTENT_FULL; + break; + + case ITEM_FIREFIELD_PVP_MEDIUM: + id = ITEM_FIREFIELD_PERSISTENT_MEDIUM; + break; + + case ITEM_FIREFIELD_PVP_SMALL: + id = ITEM_FIREFIELD_PERSISTENT_SMALL; + break; + + case ITEM_ENERGYFIELD_PVP: + id = ITEM_ENERGYFIELD_PERSISTENT; + break; + + case ITEM_POISONFIELD_PVP: + id = ITEM_POISONFIELD_PERSISTENT; + break; + + case ITEM_MAGICWALL: + id = ITEM_MAGICWALL_PERSISTENT; + break; + + case ITEM_WILDGROWTH: + id = ITEM_WILDGROWTH_PERSISTENT; + break; + + default: + break; + } + + return Item::CreateItem(id, 0); +} + +Item::Item(const uint16_t type, uint16_t count /*= 0*/) : + id(type) +{ + const ItemType& it = items[id]; + + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(count); + } else if (it.stackable) { + if (count != 0) { + setItemCount(count); + } else if (it.charges != 0) { + setItemCount(it.charges); + } + } else if (it.charges != 0) { + if (count != 0) { + setCharges(count); + } else { + setCharges(it.charges); + } + } else if (it.isKey()) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, count); + } + + setDefaultDuration(); +} + +Item::Item(const Item& i) : + Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap) +{ + if (i.attributes) { + attributes.reset(new ItemAttributes(*i.attributes)); + } +} + +Item* Item::clone() const +{ + Item* item = Item::CreateItem(id, count); + if (attributes) { + item->attributes.reset(new ItemAttributes(*attributes)); + } + return item; +} + +bool Item::equals(const Item* otherItem) const +{ + if (!otherItem || id != otherItem->id) { + return false; + } + + if (!attributes) { + return !otherItem->attributes; + } + + const auto& otherAttributes = otherItem->attributes; + if (!otherAttributes || attributes->attributeBits != otherAttributes->attributeBits) { + return false; + } + + const auto& attributeList = attributes->attributes; + const auto& otherAttributeList = otherAttributes->attributes; + for (const auto& attribute : attributeList) { + if (ItemAttributes::isStrAttrType(attribute.type)) { + for (const auto& otherAttribute : otherAttributeList) { + if (attribute.type == otherAttribute.type && *attribute.value.string != *otherAttribute.value.string) { + return false; + } + } + } else { + for (const auto& otherAttribute : otherAttributeList) { + if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) { + return false; + } + } + } + } + return true; +} + +void Item::setDefaultSubtype() +{ + const ItemType& it = items[id]; + + setItemCount(1); + + if (it.charges != 0) { + if (it.stackable) { + setItemCount(it.charges); + } else { + setCharges(it.charges); + } + } +} + +void Item::onRemoved() +{ + ScriptEnvironment::removeTempItem(this); +} + +void Item::setID(uint16_t newid) +{ + const ItemType& prevIt = Item::items[id]; + id = newid; + + const ItemType& it = Item::items[newid]; + uint32_t newDuration = it.decayTime * 1000; + + if (newDuration == 0 && !it.stopTime && it.decayTo < 0) { + removeAttribute(ITEM_ATTRIBUTE_DECAYSTATE); + removeAttribute(ITEM_ATTRIBUTE_DURATION); + } + + removeAttribute(ITEM_ATTRIBUTE_CORPSEOWNER); + + if (newDuration > 0 && (!prevIt.stopTime || !hasAttribute(ITEM_ATTRIBUTE_DURATION))) { + setDecaying(DECAYING_FALSE); + setDuration(newDuration); + } +} + +Cylinder* Item::getTopParent() +{ + Cylinder* aux = getParent(); + Cylinder* prevaux = dynamic_cast(this); + if (!aux) { + return prevaux; + } + + while (aux->getParent() != nullptr) { + prevaux = aux; + aux = aux->getParent(); + } + + if (prevaux) { + return prevaux; + } + return aux; +} + +const Cylinder* Item::getTopParent() const +{ + const Cylinder* aux = getParent(); + const Cylinder* prevaux = dynamic_cast(this); + if (!aux) { + return prevaux; + } + + while (aux->getParent() != nullptr) { + prevaux = aux; + aux = aux->getParent(); + } + + if (prevaux) { + return prevaux; + } + return aux; +} + +Tile* Item::getTile() +{ + Cylinder* cylinder = getTopParent(); + //get root cylinder + if (cylinder && cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + return dynamic_cast(cylinder); +} + +const Tile* Item::getTile() const +{ + const Cylinder* cylinder = getTopParent(); + //get root cylinder + if (cylinder && cylinder->getParent()) { + cylinder = cylinder->getParent(); + } + return dynamic_cast(cylinder); +} + +uint16_t Item::getSubType() const +{ + const ItemType& it = items[id]; + if (it.isFluidContainer() || it.isSplash()) { + return getFluidType(); + } else if (it.stackable) { + return count; + } else if (it.charges != 0) { + return getCharges(); + } + return count; +} + +Player* Item::getHoldingPlayer() const +{ + Cylinder* p = getParent(); + while (p) { + if (p->getCreature()) { + return p->getCreature()->getPlayer(); + } + + p = p->getParent(); + } + return nullptr; +} + +void Item::setSubType(uint16_t n) +{ + const ItemType& it = items[id]; + if (it.isFluidContainer() || it.isSplash()) { + setFluidType(n); + } else if (it.stackable) { + setItemCount(n); + } else if (it.charges != 0) { + setCharges(n); + } else { + setItemCount(n); + } +} + +Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + switch (attr) { + case ATTR_COUNT: + case ATTR_RUNE_CHARGES: { + uint8_t count; + if (!propStream.read(count)) { + return ATTR_READ_ERROR; + } + + setSubType(count); + break; + } + + case ATTR_ACTION_ID: { + uint16_t actionId; + if (!propStream.read(actionId)) { + return ATTR_READ_ERROR; + } + + setActionId(actionId); + break; + } + + case ATTR_MOVEMENT_ID: { + uint16_t movementId; + if (!propStream.read(movementId)) { + return ATTR_READ_ERROR; + } + + setMovementID(movementId); + break; + } + + case ATTR_TEXT: { + std::string text; + if (!propStream.readString(text)) { + return ATTR_READ_ERROR; + } + + setText(text); + break; + } + + case ATTR_WRITTENDATE: { + uint32_t writtenDate; + if (!propStream.read(writtenDate)) { + return ATTR_READ_ERROR; + } + + setDate(writtenDate); + break; + } + + case ATTR_WRITTENBY: { + std::string writer; + if (!propStream.readString(writer)) { + return ATTR_READ_ERROR; + } + + setWriter(writer); + break; + } + + case ATTR_DESC: { + std::string text; + if (!propStream.readString(text)) { + return ATTR_READ_ERROR; + } + + setSpecialDescription(text); + break; + } + + case ATTR_CHARGES: { + uint16_t charges; + if (!propStream.read(charges)) { + return ATTR_READ_ERROR; + } + + setSubType(charges); + break; + } + + case ATTR_DURATION: { + int32_t duration; + if (!propStream.read(duration)) { + return ATTR_READ_ERROR; + } + + setDuration(std::max(0, duration)); + break; + } + + case ATTR_DECAYING_STATE: { + uint8_t state; + if (!propStream.read(state)) { + return ATTR_READ_ERROR; + } + + if (state != DECAYING_FALSE) { + setDecaying(DECAYING_PENDING); + } + break; + } + + case ATTR_NAME: { + std::string name; + if (!propStream.readString(name)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_NAME, name); + break; + } + + case ATTR_ARTICLE: { + std::string article; + if (!propStream.readString(article)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_ARTICLE, article); + break; + } + + case ATTR_PLURALNAME: { + std::string pluralName; + if (!propStream.readString(pluralName)) { + return ATTR_READ_ERROR; + } + + setStrAttr(ITEM_ATTRIBUTE_PLURALNAME, pluralName); + break; + } + + case ATTR_WEIGHT: { + uint32_t weight; + if (!propStream.read(weight)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_WEIGHT, weight); + break; + } + + case ATTR_ATTACK: { + int32_t attack; + if (!propStream.read(attack)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_ATTACK, attack); + break; + } + + case ATTR_DEFENSE: { + int32_t defense; + if (!propStream.read(defense)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DEFENSE, defense); + break; + } + + case ATTR_ARMOR: { + int32_t armor; + if (!propStream.read(armor)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_ARMOR, armor); + break; + } + + case ATTR_SHOOTRANGE: { + uint8_t shootRange; + if (!propStream.read(shootRange)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE, shootRange); + break; + } + + case ATTR_KEYNUMBER: { + uint16_t keyNumber; + if (!propStream.read(keyNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber); + break; + } + + case ATTR_KEYHOLENUMBER: + { + uint16_t keyHoleNumber; + if (!propStream.read(keyHoleNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber); + break; + } + + case ATTR_DOORLEVEL: + { + uint16_t doorLevel; + if (!propStream.read(doorLevel)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel); + break; + } + + case ATTR_DOORQUESTNUMBER: + { + uint16_t doorQuestNumber; + if (!propStream.read(doorQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber); + break; + } + + case ATTR_DOORQUESTVALUE: + { + uint16_t doorQuestValue; + if (!propStream.read(doorQuestValue)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue); + break; + } + + case ATTR_CHESTQUESTNUMBER: + { + uint16_t chestQuestNumber; + if (!propStream.read(chestQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, chestQuestNumber); + break; + } + + //these should be handled through derived classes + //If these are called then something has changed in the items.xml since the map was saved + //just read the values + + //Depot class + case ATTR_DEPOT_ID: { + if (!propStream.skip(2)) { + return ATTR_READ_ERROR; + } + break; + } + + //Door class + case ATTR_HOUSEDOORID: { + if (!propStream.skip(1)) { + return ATTR_READ_ERROR; + } + break; + } + + //Bed class + case ATTR_SLEEPERGUID: { + if (!propStream.skip(4)) { + return ATTR_READ_ERROR; + } + break; + } + + case ATTR_SLEEPSTART: { + if (!propStream.skip(4)) { + return ATTR_READ_ERROR; + } + break; + } + + //Teleport class + case ATTR_TELE_DEST: { + if (!propStream.skip(5)) { + return ATTR_READ_ERROR; + } + break; + } + + //Container class + case ATTR_CONTAINER_ITEMS: { + return ATTR_READ_ERROR; + } + + default: + return ATTR_READ_ERROR; + } + + return ATTR_READ_CONTINUE; +} + +bool Item::unserializeAttr(PropStream& propStream) +{ + uint8_t attr_type; + while (propStream.read(attr_type) && attr_type != 0) { + Attr_ReadValue ret = readAttr(static_cast(attr_type), propStream); + if (ret == ATTR_READ_ERROR) { + return false; + } else if (ret == ATTR_READ_END) { + return true; + } + } + return true; +} + +bool Item::unserializeItemNode(FileLoader&, NODE, PropStream& propStream) +{ + return unserializeAttr(propStream); +} + +void Item::serializeAttr(PropWriteStream& propWriteStream) const +{ + const ItemType& it = items[id]; + if (it.stackable || it.isFluidContainer() || it.isSplash()) { + propWriteStream.write(ATTR_COUNT); + propWriteStream.write(getSubType()); + } + + uint16_t charges = getCharges(); + if (charges != 0) { + propWriteStream.write(ATTR_CHARGES); + propWriteStream.write(charges); + } + + if (it.moveable) { + uint16_t actionId = getActionId(); + if (actionId != 0) { + propWriteStream.write(ATTR_ACTION_ID); + propWriteStream.write(actionId); + } + } + + const std::string& text = getText(); + if (!text.empty()) { + propWriteStream.write(ATTR_TEXT); + propWriteStream.writeString(text); + } + + const time_t writtenDate = getDate(); + if (writtenDate != 0) { + propWriteStream.write(ATTR_WRITTENDATE); + propWriteStream.write(writtenDate); + } + + const std::string& writer = getWriter(); + if (!writer.empty()) { + propWriteStream.write(ATTR_WRITTENBY); + propWriteStream.writeString(writer); + } + + const std::string& specialDesc = getSpecialDescription(); + if (!specialDesc.empty()) { + propWriteStream.write(ATTR_DESC); + propWriteStream.writeString(specialDesc); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) { + propWriteStream.write(ATTR_DURATION); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DURATION)); + } + + ItemDecayState_t decayState = getDecaying(); + if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) { + propWriteStream.write(ATTR_DECAYING_STATE); + propWriteStream.write(decayState); + } + + if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { + propWriteStream.write(ATTR_NAME); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_NAME)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { + propWriteStream.write(ATTR_ARTICLE); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_ARTICLE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { + propWriteStream.write(ATTR_PLURALNAME); + propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_PLURALNAME)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { + propWriteStream.write(ATTR_WEIGHT); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_WEIGHT)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { + propWriteStream.write(ATTR_ATTACK); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { + propWriteStream.write(ATTR_DEFENSE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { + propWriteStream.write(ATTR_ARMOR); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ARMOR)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { + propWriteStream.write(ATTR_SHOOTRANGE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) { + propWriteStream.write(ATTR_KEYNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) { + propWriteStream.write(ATTR_KEYHOLENUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { + propWriteStream.write(ATTR_DOORLEVEL); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) { + propWriteStream.write(ATTR_DOORQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) { + propWriteStream.write(ATTR_DOORQUESTVALUE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) { + propWriteStream.write(ATTR_CHESTQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + } +} + +bool Item::hasProperty(ITEMPROPERTY prop) const +{ + const ItemType& it = items[id]; + switch (prop) { + case CONST_PROP_BLOCKSOLID: return it.blockSolid; + case CONST_PROP_MOVEABLE: return it.moveable; + case CONST_PROP_HASHEIGHT: return it.hasHeight; + case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; + case CONST_PROP_BLOCKPATH: return it.blockPathFind; + case CONST_PROP_ISVERTICAL: return it.isVertical; + case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; + case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && !it.moveable; + case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && !it.moveable; + case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && !it.moveable; + case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; + case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; + case CONST_PROP_UNLAY: return !it.allowPickupable; + default: return false; + } +} + +uint32_t Item::getWeight() const +{ + uint32_t weight = getBaseWeight(); + if (isStackable()) { + return weight * std::max(1, getItemCount()); + } + return weight; +} + +std::string Item::getDescription(const ItemType& it, int32_t lookDistance, + const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + std::ostringstream s; + s << getNameDescription(it, item, subType, addArticle); + + if (item) { + subType = item->getSubType(); + } + + if (it.isRune()) { + uint32_t charges = std::max(static_cast(1), static_cast(item == nullptr ? it.charges : item->getCharges())); + + if (it.runeLevel > 0) { + s << " for level " << it.runeLevel; + } + + if (it.runeLevel > 0) { + s << " and"; + } + + s << " for magic level " << it.runeMagLevel; + s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; + } 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) { + 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)) { + s << " ("; + if (it.attack != 0) { + s << "Atk:" << static_cast(it.attack); + } + + if (it.defense != 0) { + if (it.attack != 0) + s << " "; + + s << "Def:" << static_cast(it.defense); + } + + s << ")"; + } + s << "."; + } else if (it.armor != 0) { + if (it.charges > 0) { + if (subType > 1) { + s << " that has " << static_cast(subType) << " charges left"; + } else { + s << " that has " << it.charges << " charge left"; + } + } + + s << " (Arm:" << it.armor << ")."; + } else if (it.isFluidContainer()) { + if (item && item->getFluidType() != 0) { + s << " of " << items[item->getFluidType()].name << "."; + } else { + s << ". It is empty."; + } + } else if (it.isSplash()) { + s << " of "; + if (item && item->getFluidType() != 0) { + s << items[item->getFluidType()].name; + } else { + s << items[1].name; + } + s << "."; + } else if (it.isContainer() && !it.isChest()) { + s << " (Vol:" << static_cast(it.maxItems) << ")."; + } else if (it.isKey()) { + if (item) { + s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; + } else { + s << " (Key:0)."; + } + } else if (it.allowDistRead) { + s << "."; + s << std::endl; + + if (item && item->getText() != "") { + if (lookDistance <= 4) { + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + s << writer << " wrote"; + time_t date = item->getDate(); + if (date != 0) { + s << " on " << formatDateShort(date); + } + s << ": "; + } else { + s << "You read: "; + } + s << item->getText(); + } else { + s << "You are too far away to read it."; + } + } else { + s << "Nothing is written on it."; + } + } 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 { + s << " that has 1 charge left."; + } + } 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) { + s << "1 minute left."; + } else { + s << "less than a minute left."; + } + } else { + s << " that is brand-new."; + } + } else { + s << "."; + } + + if (it.wieldInfo != 0) { + s << std::endl << "It can only be wielded properly by "; + + if (it.wieldInfo & WIELDINFO_PREMIUM) { + s << "premium "; + } + + if (it.wieldInfo & WIELDINFO_VOCREQ) { + s << it.vocationString; + } else { + s << "players"; + } + + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " of level " << static_cast(it.minReqLevel) << " or higher"; + } + + if (it.wieldInfo & WIELDINFO_MAGLV) { + if (it.wieldInfo & WIELDINFO_LEVEL) { + s << " and"; + } else { + s << " of"; + } + + s << " magic level " << static_cast(it.minReqMagicLevel) << " or higher"; + } + + s << "."; + } + + if (lookDistance <= 1 && !it.isChest() && it.pickupable) { + double weight = (item == nullptr ? it.weight : item->getWeight()); + if (weight > 0) { + s << std::endl << getWeightDescription(it, weight); + } + } + + if (item && item->getSpecialDescription() != "") { + s << std::endl << item->getSpecialDescription().c_str(); + } else if (it.description.length() && lookDistance <= 1) { + s << std::endl << it.description << "."; + } + + return s.str(); +} + +std::string Item::getDescription(int32_t lookDistance) const +{ + const ItemType& it = items[id]; + return getDescription(it, lookDistance, this); +} + +std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) +{ + if (item) { + subType = item->getSubType(); + } + + std::ostringstream s; + + const std::string& name = (item ? item->getName() : it.name); + if (!name.empty()) { + if (it.stackable && subType > 1) { + if (it.showCount) { + s << subType << ' '; + } + + s << (item ? item->getPluralName() : it.getPluralName()); + } else { + if (addArticle) { + const std::string& article = (item ? item->getArticle() : it.article); + if (!article.empty()) { + s << article << ' '; + } + } + + s << name; + } + } else { + s << "an item of type " << it.id; + } + return s.str(); +} + +std::string Item::getNameDescription() const +{ + const ItemType& it = items[id]; + return getNameDescription(it, this); +} + +std::string Item::getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count /*= 1*/) +{ + std::ostringstream ss; + if (it.stackable && count > 1 && it.showCount != 0) { + ss << "They weigh "; + } else { + ss << "It weighs "; + } + + if (weight < 10) { + ss << "0.0" << weight; + } else if (weight < 100) { + ss << "0." << weight; + } else { + std::string weightString = std::to_string(weight); + weightString.insert(weightString.end() - 2, '.'); + ss << weightString; + } + + ss << " oz."; + return ss.str(); +} + +std::string Item::getWeightDescription(uint32_t weight) const +{ + const ItemType& it = Item::items[id]; + return getWeightDescription(it, weight, getItemCount()); +} + +std::string Item::getWeightDescription() const +{ + uint32_t weight = getWeight(); + if (weight == 0) { + return std::string(); + } + return getWeightDescription(weight); +} + +bool Item::canDecay() const +{ + if (isRemoved()) { + return false; + } + + const ItemType& it = Item::items[id]; + if (it.decayTo < 0 || it.decayTime == 0) { + return false; + } + + return true; +} + +uint32_t Item::getWorth() const +{ + switch (id) { + case ITEM_GOLD_COIN: + return count; + + case ITEM_PLATINUM_COIN: + return count * 100; + + case ITEM_CRYSTAL_COIN: + return count * 10000; + + default: + return 0; + } +} + +void Item::getLight(LightInfo& lightInfo) const +{ + const ItemType& it = items[id]; + lightInfo.color = it.lightColor; + lightInfo.level = it.lightLevel; +} + +std::string ItemAttributes::emptyString; + +const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const +{ + if (!isStrAttrType(type)) { + return emptyString; + } + + const Attribute* attr = getExistingAttr(type); + if (!attr) { + return emptyString; + } + return *attr->value.string; +} + +void ItemAttributes::setStrAttr(itemAttrTypes type, const std::string& value) +{ + if (!isStrAttrType(type)) { + return; + } + + if (value.empty()) { + return; + } + + Attribute& attr = getAttr(type); + delete attr.value.string; + attr.value.string = new std::string(value); +} + +void ItemAttributes::removeAttribute(itemAttrTypes type) +{ + if (!hasAttribute(type)) { + return; + } + + auto prev_it = attributes.cbegin(); + if ((*prev_it).type == type) { + attributes.pop_front(); + } else { + auto it = prev_it, end = attributes.cend(); + while (++it != end) { + if ((*it).type == type) { + attributes.erase_after(prev_it); + break; + } + prev_it = it; + } + } + attributeBits &= ~type; +} + +int64_t ItemAttributes::getIntAttr(itemAttrTypes type) const +{ + if (!isIntAttrType(type)) { + return 0; + } + + const Attribute* attr = getExistingAttr(type); + if (!attr) { + return 0; + } + return attr->value.integer; +} + +void ItemAttributes::setIntAttr(itemAttrTypes type, int64_t value) +{ + if (!isIntAttrType(type)) { + return; + } + + getAttr(type).value.integer = value; +} + +void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value) +{ + if (!isIntAttrType(type)) { + return; + } + + getAttr(type).value.integer += value; +} + +const ItemAttributes::Attribute* ItemAttributes::getExistingAttr(itemAttrTypes type) const +{ + if (hasAttribute(type)) { + for (const Attribute& attribute : attributes) { + if (attribute.type == type) { + return &attribute; + } + } + } + return nullptr; +} + +ItemAttributes::Attribute& ItemAttributes::getAttr(itemAttrTypes type) +{ + if (hasAttribute(type)) { + for (Attribute& attribute : attributes) { + if (attribute.type == type) { + return attribute; + } + } + } + + attributeBits |= type; + attributes.emplace_front(type); + return attributes.front(); +} + +void Item::startDecaying() +{ + g_game.startDecay(this); +} diff --git a/src/item.h b/src/item.h new file mode 100644 index 0000000..45e1e58 --- /dev/null +++ b/src/item.h @@ -0,0 +1,835 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 +#define FS_ITEM_H_009A319FB13D477D9EEFFBBD9BB83562 + +#include "cylinder.h" +#include "thing.h" +#include "items.h" + +#include + +class Creature; +class Player; +class Container; +class Depot; +class Teleport; +class Mailbox; +class Door; +class MagicField; +class BedItem; + +enum ITEMPROPERTY { + CONST_PROP_BLOCKSOLID = 0, + CONST_PROP_HASHEIGHT, + CONST_PROP_BLOCKPROJECTILE, + CONST_PROP_BLOCKPATH, + CONST_PROP_ISVERTICAL, + CONST_PROP_ISHORIZONTAL, + CONST_PROP_MOVEABLE, + CONST_PROP_IMMOVABLEBLOCKSOLID, + CONST_PROP_IMMOVABLEBLOCKPATH, + CONST_PROP_IMMOVABLENOFIELDBLOCKPATH, + CONST_PROP_NOFIELDBLOCKPATH, + CONST_PROP_SUPPORTHANGABLE, + CONST_PROP_UNLAY, +}; + +enum TradeEvents_t { + ON_TRADE_TRANSFER, + ON_TRADE_CANCEL, +}; + +enum ItemDecayState_t : uint8_t { + DECAYING_FALSE = 0, + DECAYING_TRUE, + DECAYING_PENDING, +}; + +enum AttrTypes_t { + //ATTR_DESCRIPTION = 1, + //ATTR_EXT_FILE = 2, + ATTR_TILE_FLAGS = 3, + ATTR_ACTION_ID = 4, + ATTR_MOVEMENT_ID = 5, + ATTR_TEXT = 6, + ATTR_DESC = 7, + ATTR_TELE_DEST = 8, + ATTR_ITEM = 9, + ATTR_DEPOT_ID = 10, + //ATTR_EXT_SPAWN_FILE = 11, + ATTR_RUNE_CHARGES = 12, + //ATTR_EXT_HOUSE_FILE = 13, + ATTR_HOUSEDOORID = 14, + ATTR_COUNT = 15, + ATTR_DURATION = 16, + ATTR_DECAYING_STATE = 17, + ATTR_WRITTENDATE = 18, + ATTR_WRITTENBY = 19, + ATTR_SLEEPERGUID = 20, + ATTR_SLEEPSTART = 21, + ATTR_CHARGES = 22, + ATTR_KEYNUMBER = 23, + ATTR_KEYHOLENUMBER = 24, + ATTR_DOORQUESTNUMBER = 25, + ATTR_DOORQUESTVALUE = 26, + ATTR_DOORLEVEL = 27, + ATTR_CHESTQUESTNUMBER = 28, + // add non-OTBM attributes after here + ATTR_CONTAINER_ITEMS = 29, + ATTR_NAME = 30, + ATTR_ARTICLE = 31, + ATTR_PLURALNAME = 32, + ATTR_WEIGHT = 33, + ATTR_ATTACK = 34, + ATTR_DEFENSE = 35, + ATTR_ARMOR = 36, + ATTR_SHOOTRANGE = 37, +}; + +enum Attr_ReadValue { + ATTR_READ_CONTINUE, + ATTR_READ_ERROR, + ATTR_READ_END, +}; + +class ItemAttributes +{ + public: + ItemAttributes() = default; + + void setSpecialDescription(const std::string& desc) { + setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); + } + const std::string& getSpecialDescription() const { + return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); + } + + void setText(const std::string& text) { + setStrAttr(ITEM_ATTRIBUTE_TEXT, text); + } + void resetText() { + removeAttribute(ITEM_ATTRIBUTE_TEXT); + } + const std::string& getText() const { + return getStrAttr(ITEM_ATTRIBUTE_TEXT); + } + + void setDate(int32_t n) { + setIntAttr(ITEM_ATTRIBUTE_DATE, n); + } + void resetDate() { + removeAttribute(ITEM_ATTRIBUTE_DATE); + } + time_t getDate() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); + } + + void setWriter(const std::string& writer) { + setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); + } + void resetWriter() { + removeAttribute(ITEM_ATTRIBUTE_WRITER); + } + const std::string& getWriter() const { + return getStrAttr(ITEM_ATTRIBUTE_WRITER); + } + + void setActionId(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); + } + uint16_t getActionId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); + } + + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + } + uint16_t getMovementId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + } + + void setKeyNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, n); + } + uint16_t getKeyNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + } + + void setKeyHoleNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, n); + } + uint16_t getKeyHoleNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } + + void setDoorQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, n); + } + uint16_t getDoorQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + void setDoorQuestValue(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, n); + } + uint16_t getDoorQuestValue() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + void setDoorLevel(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, n); + } + uint16_t getDoorLevel() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + void setChestQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, n); + } + uint16_t getChestQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + } + + void setCharges(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); + } + uint16_t getCharges() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); + } + + void setFluidType(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); + } + uint16_t getFluidType() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } + + void setOwner(uint32_t owner) { + setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); + } + uint32_t getOwner() const { + return getIntAttr(ITEM_ATTRIBUTE_OWNER); + } + + void setDuration(int32_t time) { + setIntAttr(ITEM_ATTRIBUTE_DURATION, time); + } + void decreaseDuration(int32_t time) { + increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); + } + uint32_t getDuration() const { + return getIntAttr(ITEM_ATTRIBUTE_DURATION); + } + + void setDecaying(ItemDecayState_t decayState) { + setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); + } + ItemDecayState_t getDecaying() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } + + protected: + inline bool hasAttribute(itemAttrTypes type) const { + return (type & attributeBits) != 0; + } + void removeAttribute(itemAttrTypes type); + + static std::string emptyString; + + struct Attribute + { + union { + int64_t integer; + std::string* string; + } value; + itemAttrTypes type; + + explicit Attribute(itemAttrTypes type) : type(type) { + memset(&value, 0, sizeof(value)); + } + Attribute(const Attribute& i) { + type = i.type; + if (ItemAttributes::isIntAttrType(type)) { + value.integer = i.value.integer; + } else if (ItemAttributes::isStrAttrType(type)) { + value.string = new std::string(*i.value.string); + } else { + memset(&value, 0, sizeof(value)); + } + } + Attribute(Attribute&& attribute) : value(attribute.value), type(attribute.type) { + memset(&attribute.value, 0, sizeof(value)); + attribute.type = ITEM_ATTRIBUTE_NONE; + } + ~Attribute() { + if (ItemAttributes::isStrAttrType(type)) { + delete value.string; + } + } + Attribute& operator=(Attribute other) { + Attribute::swap(*this, other); + return *this; + } + Attribute& operator=(Attribute&& other) { + if (this != &other) { + if (ItemAttributes::isStrAttrType(type)) { + delete value.string; + } + + value = other.value; + type = other.type; + + memset(&other.value, 0, sizeof(value)); + other.type = ITEM_ATTRIBUTE_NONE; + } + return *this; + } + + static void swap(Attribute& first, Attribute& second) { + std::swap(first.value, second.value); + std::swap(first.type, second.type); + } + }; + + std::forward_list attributes; + uint32_t attributeBits = 0; + + const std::string& getStrAttr(itemAttrTypes type) const; + void setStrAttr(itemAttrTypes type, const std::string& value); + + int64_t getIntAttr(itemAttrTypes type) const; + void setIntAttr(itemAttrTypes type, int64_t value); + void increaseIntAttr(itemAttrTypes type, int64_t value); + + const Attribute* getExistingAttr(itemAttrTypes type) const; + Attribute& getAttr(itemAttrTypes type); + + public: + inline static bool isIntAttrType(itemAttrTypes type) { + return (type & 0xFFFFE13) != 0; + } + inline static bool isStrAttrType(itemAttrTypes type) { + return (type & 0x1EC) != 0; + } + + const std::forward_list& getList() const { + return attributes; + } + + friend class Item; +}; + +class Item : virtual public Thing +{ + public: + //Factory member to create item of right type based on type + static Item* CreateItem(const uint16_t type, uint16_t count = 0); + static Container* CreateItemAsContainer(const uint16_t type, uint16_t size); + static Item* CreateItem(PropStream& propStream); + static Items items; + + // Constructor for items + Item(const uint16_t type, uint16_t count = 0); + Item(const Item& i); + virtual Item* clone() const; + + virtual ~Item() = default; + + // non-assignable + Item& operator=(const Item&) = delete; + + bool equals(const Item* otherItem) const; + + Item* getItem() final { + return this; + } + const Item* getItem() const final { + return this; + } + virtual Teleport* getTeleport() { + return nullptr; + } + virtual const Teleport* getTeleport() const { + return nullptr; + } + virtual Mailbox* getMailbox() { + return nullptr; + } + virtual const Mailbox* getMailbox() const { + return nullptr; + } + virtual Door* getDoor() { + return nullptr; + } + virtual const Door* getDoor() const { + return nullptr; + } + virtual MagicField* getMagicField() { + return nullptr; + } + virtual const MagicField* getMagicField() const { + return nullptr; + } + virtual BedItem* getBed() { + return nullptr; + } + virtual const BedItem* getBed() const { + return nullptr; + } + + const std::string& getStrAttr(itemAttrTypes type) const { + if (!attributes) { + return ItemAttributes::emptyString; + } + return attributes->getStrAttr(type); + } + void setStrAttr(itemAttrTypes type, const std::string& value) { + getAttributes()->setStrAttr(type, value); + } + + int32_t getIntAttr(itemAttrTypes type) const { + if (!attributes) { + return 0; + } + return attributes->getIntAttr(type); + } + void setIntAttr(itemAttrTypes type, int32_t value) { + getAttributes()->setIntAttr(type, value); + } + void increaseIntAttr(itemAttrTypes type, int32_t value) { + getAttributes()->increaseIntAttr(type, value); + } + + void removeAttribute(itemAttrTypes type) { + if (attributes) { + attributes->removeAttribute(type); + } + } + bool hasAttribute(itemAttrTypes type) const { + if (!attributes) { + return false; + } + return attributes->hasAttribute(type); + } + + void setSpecialDescription(const std::string& desc) { + setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); + } + const std::string& getSpecialDescription() const { + return getStrAttr(ITEM_ATTRIBUTE_DESCRIPTION); + } + + void setText(const std::string& text) { + setStrAttr(ITEM_ATTRIBUTE_TEXT, text); + } + void resetText() { + removeAttribute(ITEM_ATTRIBUTE_TEXT); + } + const std::string& getText() const { + return getStrAttr(ITEM_ATTRIBUTE_TEXT); + } + + void setDate(int32_t n) { + setIntAttr(ITEM_ATTRIBUTE_DATE, n); + } + void resetDate() { + removeAttribute(ITEM_ATTRIBUTE_DATE); + } + time_t getDate() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DATE)); + } + + void setWriter(const std::string& writer) { + setStrAttr(ITEM_ATTRIBUTE_WRITER, writer); + } + void resetWriter() { + removeAttribute(ITEM_ATTRIBUTE_WRITER); + } + const std::string& getWriter() const { + return getStrAttr(ITEM_ATTRIBUTE_WRITER); + } + + void setActionId(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); + } + uint16_t getActionId() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); + } + + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + } + uint16_t getMovementId() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + } + + void setCharges(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHARGES, n); + } + uint16_t getCharges() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHARGES)); + } + + void setFluidType(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE, n); + } + uint16_t getFluidType() const { + if (!attributes) { + return 0; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_FLUIDTYPE)); + } + + void setOwner(uint32_t owner) { + setIntAttr(ITEM_ATTRIBUTE_OWNER, owner); + } + uint32_t getOwner() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_OWNER); + } + + void setCorpseOwner(uint32_t corpseOwner) { + setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); + } + uint32_t getCorpseOwner() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); + } + + void setDuration(int32_t time) { + setIntAttr(ITEM_ATTRIBUTE_DURATION, time); + } + void decreaseDuration(int32_t time) { + increaseIntAttr(ITEM_ATTRIBUTE_DURATION, -time); + } + uint32_t getDuration() const { + if (!attributes) { + return 0; + } + return getIntAttr(ITEM_ATTRIBUTE_DURATION); + } + + void setDecaying(ItemDecayState_t decayState) { + setIntAttr(ITEM_ATTRIBUTE_DECAYSTATE, decayState); + } + ItemDecayState_t getDecaying() const { + if (!attributes) { + return DECAYING_FALSE; + } + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); + } + + static std::string getDescription(const ItemType& it, int32_t lookDistance, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); + static std::string getNameDescription(const ItemType& it, const Item* item = nullptr, int32_t subType = -1, bool addArticle = true); + static std::string getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count = 1); + + std::string getDescription(int32_t lookDistance) const final; + std::string getNameDescription() const; + std::string getWeightDescription() const; + + //serialization + virtual Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream); + bool unserializeAttr(PropStream& propStream); + virtual bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream); + + virtual void serializeAttr(PropWriteStream& propWriteStream) const; + + bool isPushable() const final { + return isMoveable(); + } + int32_t getThrowRange() const final { + return (isPickupable() ? 15 : 2); + } + + uint16_t getID() const { + return id; + } + void setID(uint16_t newid); + + // Returns the player that is holding this item in his inventory + Player* getHoldingPlayer() const; + + CombatType_t getDamageType() const { + return items[id].damageType; + } + CombatType_t getCombatType() const { + return items[id].combatType; + } + WeaponType_t getWeaponType() const { + return items[id].weaponType; + } + Ammo_t getAmmoType() const { + return items[id].ammoType; + } + uint8_t getShootRange() const { + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { + return getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE); + } + return items[id].shootRange; + } + uint8_t getMissileType() const { + return items[id].shootType; + } + uint8_t getFragility() const { + return items[id].fragility; + } + + int32_t getAttackStrength() const { + return items[id].attackStrength; + } + int32_t getAttackVariation() const { + return items[id].attackVariation; + } + int32_t getManaConsumption() const { + return items[id].manaConsumption; + } + uint32_t getMinimumLevel() const { + return items[id].minReqLevel; + } + int32_t getWeaponSpecialEffect() const { + return items[id].weaponSpecialEffect; + } + virtual uint32_t getWeight() const; + uint32_t getBaseWeight() const { + if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { + return getIntAttr(ITEM_ATTRIBUTE_WEIGHT); + } + return items[id].weight; + } + int32_t getAttack() const { + if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { + return getIntAttr(ITEM_ATTRIBUTE_ATTACK); + } + return items[id].attack; + } + int32_t getArmor() const { + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { + return getIntAttr(ITEM_ATTRIBUTE_ARMOR); + } + return items[id].armor; + } + int32_t getDefense() const { + if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { + return getIntAttr(ITEM_ATTRIBUTE_DEFENSE); + } + return items[id].defense; + } + int32_t getSlotPosition() const { + return items[id].slotPosition; + } + uint16_t getDisguiseId() const { + return items[id].disguiseId; + } + + uint32_t getWorth() const; + void getLight(LightInfo& lightInfo) const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool isBlocking() const { + return items[id].blockSolid; + } + bool isStackable() const { + return items[id].stackable; + } + bool isAlwaysOnTop() const { + return items[id].alwaysOnTop; + } + bool isGroundTile() const { + return items[id].isGroundTile(); + } + bool isMagicField() const { + return items[id].isMagicField(); + } + bool isSplash() const { + return items[id].isSplash(); + } + bool isMoveable() const { + return items[id].moveable; + } + bool isPickupable() const { + return items[id].pickupable; + } + bool isHangable() const { + return items[id].isHangable; + } + bool isRotatable() const { + const ItemType& it = items[id]; + return it.rotatable && it.rotateTo; + } + bool isDisguised() const { + return items[id].disguise; + } + bool isChangeUse() const { + return items[id].changeUse; + } + bool isChestQuest() const { + return items[id].isChest(); + } + bool hasCollisionEvent() const { + return items[id].collisionEvent; + } + bool hasSeparationEvent() const { + return items[id].separationEvent; + } + bool hasUseEvent() const { + return items[id].useEvent; + } + bool hasMultiUseEvent() const { + return items[id].multiUseEvent; + } + bool canDistUse() const { + return items[id].distUse; + } + + const std::string& getName() const { + if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { + return getStrAttr(ITEM_ATTRIBUTE_NAME); + } + return items[id].name; + } + const std::string getPluralName() const { + if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { + return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME); + } + return items[id].getPluralName(); + } + const std::string& getArticle() const { + if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { + return getStrAttr(ITEM_ATTRIBUTE_ARTICLE); + } + return items[id].article; + } + + // get the number of items + uint16_t getItemCount() const { + return count; + } + void setItemCount(uint8_t n) { + count = n; + } + + static uint32_t countByType(const Item* i, int32_t subType); + + void setDefaultSubtype(); + uint16_t getSubType() const; + void setSubType(uint16_t n); + + void setDefaultDuration() { + uint32_t duration = getDefaultDuration(); + if (duration != 0) { + setDuration(duration); + } + } + uint32_t getDefaultDuration() const { + return items[id].decayTime * 1000; + } + bool canDecay() const; + + virtual bool canRemove() const { + return true; + } + virtual bool canTransform() const { + return true; + } + virtual void onRemoved(); + virtual void onTradeEvent(TradeEvents_t, Player*) {} + + virtual void startDecaying(); + + void setLoadedFromMap(bool value) { + loadedFromMap = value; + } + bool isCleanable() const { + return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + } + + std::unique_ptr& getAttributes() { + if (!attributes) { + attributes.reset(new ItemAttributes()); + } + return attributes; + } + + void incrementReferenceCounter() { + ++referenceCounter; + } + void decrementReferenceCounter() { + if (--referenceCounter == 0) { + delete this; + } + } + + Cylinder* getParent() const { + return parent; + } + void setParent(Cylinder* cylinder) { + parent = cylinder; + } + Cylinder* getTopParent(); + const Cylinder* getTopParent() const; + Tile* getTile(); + const Tile* getTile() const; + bool isRemoved() const { + return !parent || parent->isRemoved(); + } + + protected: + std::string getWeightDescription(uint32_t weight) const; + + Cylinder* parent = nullptr; + std::unique_ptr attributes; + + uint32_t referenceCounter = 0; + + uint16_t id; // the same id as in ItemType + uint8_t count = 1; // number of stacked items + + bool loadedFromMap = false; + + //Don't add variables here, use the ItemAttribute class. +}; + +typedef std::list ItemList; +typedef std::deque ItemDeque; + +inline uint32_t Item::countByType(const Item* i, int32_t subType) +{ + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; +} + +#endif diff --git a/src/items.cpp b/src/items.cpp new file mode 100644 index 0000000..dcb1463 --- /dev/null +++ b/src/items.cpp @@ -0,0 +1,606 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "items.h" +#include "spells.h" +#include "movement.h" +#include "script.h" + +#include "pugicast.h" + +extern MoveEvents* g_moveEvents; + +Items::Items() +{ + items.reserve(6000); + nameToItems.reserve(6000); +} + +void Items::clear() +{ + items.clear(); + nameToItems.clear(); +} + +bool Items::reload() +{ + clear(); + if (!loadItems()) { + return false; + } + + g_moveEvents->reload(); + return true; +} + +bool Items::loadItems() +{ + ScriptReader script; + if (!script.open("data/items/items.srv")) { + return false; + } + + std::string identifier; + uint16_t id = 0; + while (true) { + script.nextToken(); + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token != IDENTIFIER) { + script.error("Identifier expected"); + return false; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "typeid") { + id = script.readNumber(); + if (id >= items.size()) { + items.resize(id + 1); + } + + if (items[id].id) { + script.error("item type already defined"); + return false; + } + + items[id].id = id; + } else if (identifier == "name") { + items[id].name = script.readString(); + } else if (identifier == "description") { + items[id].description = script.readString(); + } else if (identifier == "flags") { + script.readSymbol('{'); + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + + if (identifier == "bank") { + items[id].group = ITEM_GROUP_GROUND; + } else if (identifier == "clip") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 1; + } else if (identifier == "bottom") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 2; + } else if (identifier == "top") { + items[id].alwaysOnTop = true; + items[id].alwaysOnTopOrder = 3; + } else if (identifier == "container") { + items[id].type = ITEM_TYPE_CONTAINER; + } else if (identifier == "chest") { + items[id].type = ITEM_TYPE_CHEST; + } else if (identifier == "cumulative") { + items[id].stackable = true; + } else if (identifier == "changeuse") { + items[id].changeUse = true; + } else if (identifier == "forceuse") { + items[id].forceUse = true; + } else if (identifier == "key") { + items[id].type = ITEM_TYPE_KEY; + items[id].group = ITEM_GROUP_KEY; + } else if (identifier == "door") { + items[id].type = ITEM_TYPE_DOOR; + } else if (identifier == "bed") { + items[id].type = ITEM_TYPE_BED; + } else if (identifier == "rune") { + items[id].type = ITEM_TYPE_RUNE; + } else if (identifier == "depot") { + items[id].type = ITEM_TYPE_DEPOT; + } else if (identifier == "mailbox") { + items[id].type = ITEM_TYPE_MAILBOX; + } else if (identifier == "allowdistread") { + items[id].allowDistRead = true; + } else if (identifier == "text") { + items[id].canReadText = true; + } else if (identifier == "write") { + items[id].canWriteText = true; + } else if (identifier == "writeonce") { + items[id].canWriteText = true; + items[id].writeOnceItemId = id; + } else if (identifier == "fluidcontainer") { + items[id].group = ITEM_GROUP_FLUID; + } else if (identifier == "splash") { + items[id].group = ITEM_GROUP_SPLASH; + } else if (identifier == "unpass") { + items[id].blockSolid = true; + } else if (identifier == "unmove") { + items[id].moveable = false; + } else if (identifier == "unthrow") { + items[id].blockProjectile = true; + } else if (identifier == "unlay") { + items[id].allowPickupable = false; + } else if (identifier == "avoid") { + items[id].blockPathFind = true; + } else if (identifier == "magicfield") { + items[id].type = ITEM_TYPE_MAGICFIELD; + items[id].group = ITEM_GROUP_MAGICFIELD; + } else if (identifier == "take") { + items[id].pickupable = true; + } else if (identifier == "hang") { + items[id].isHangable = true; + } else if (identifier == "hooksouth") { + items[id].isHorizontal = true; + } else if (identifier == "hookeast") { + items[id].isVertical = true; + } else if (identifier == "rotate") { + items[id].rotatable = true; + } else if (identifier == "destroy") { + items[id].destroy = true; + } else if (identifier == "corpse") { + items[id].corpse = true; + } else if (identifier == "expire") { + items[id].stopTime = false; + } else if (identifier == "expirestop") { + items[id].stopTime = true; + } else if (identifier == "weapon") { + items[id].group = ITEM_GROUP_WEAPON; + } else if (identifier == "shield") { + items[id].weaponType = WEAPON_SHIELD; + } else if (identifier == "distance") { + items[id].weaponType = WEAPON_DISTANCE; + } else if (identifier == "wand") { + items[id].weaponType = WEAPON_WAND; + } else if (identifier == "ammo") { + items[id].weaponType = WEAPON_AMMO; + } else if (identifier == "armor") { + items[id].group = ITEM_GROUP_ARMOR; + } else if (identifier == "height") { + items[id].hasHeight = true; + } else if (identifier == "disguise") { + items[id].disguise = true; + } else if (identifier == "showdetail") { + items[id].showDuration = true; + } else if (identifier == "noreplace") { + items[id].replaceable = false; + } else if (identifier == "collisionevent") { + items[id].collisionEvent = true; + } else if (identifier == "separationevent") { + items[id].separationEvent = true; + } else if (identifier == "useevent") { + items[id].useEvent = true; + } else if (identifier == "distuse") { + items[id].distUse = true; + } else if (identifier == "multiuse") { + items[id].multiUseEvent = true; + } else { + script.error("Unknown flag"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + } else if (identifier == "attributes") { + script.readSymbol('{'); + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "waypoints") { + items[id].speed = script.readNumber(); + } else if (identifier == "capacity") { + items[id].maxItems = script.readNumber(); + } else if (identifier == "changetarget") { + items[id].transformToOnUse = script.readNumber(); + } else if (identifier == "nutrition") { + items[id].nutrition = script.readNumber(); + } else if (identifier == "maxlength") { + items[id].maxTextLen = script.readNumber(); + } else if (identifier == "fluidsource") { + items[id].fluidSource = getFluidType(script.readIdentifier()); + } else if (identifier == "avoiddamagetypes") { + items[id].combatType = getCombatType(script.readIdentifier()); + } else if (identifier == "damagetype") { + items[id].damageType = getCombatType(script.readIdentifier()); + } else if (identifier == "attackstrength") { + items[id].attackStrength = script.readNumber(); + } else if (identifier == "attackvariation") { + items[id].attackVariation = script.readNumber(); + } else if (identifier == "manaconsumption") { + items[id].manaConsumption = script.readNumber(); + } else if (identifier == "minimumlevel") { + items[id].minReqLevel = script.readNumber(); + items[id].wieldInfo |= WIELDINFO_LEVEL; + } else if (identifier == "vocations") { + int32_t vocations = script.readNumber(); + items[id].vocations = vocations; + + std::list vocationStringList; + + if (hasBitSet(VOCATION_SORCERER, vocations)) { + vocationStringList.push_back("sorcerer"); + } + + if (hasBitSet(VOCATION_DRUID, vocations)) { + vocationStringList.push_back("druid"); + } + + if (hasBitSet(VOCATION_PALADIN, vocations)) { + vocationStringList.push_back("paladin"); + } + + if (hasBitSet(VOCATION_KNIGHT, vocations)) { + vocationStringList.push_back("knight"); + } + + std::string vocationString; + for (const std::string& str : vocationStringList) { + if (!vocationString.empty()) { + if (str != vocationStringList.back()) { + vocationString.push_back(','); + vocationString.push_back(' '); + } else { + vocationString += " and "; + } + } + + vocationString += str; + vocationString.push_back('s'); + } + + items[id].wieldInfo |= WIELDINFO_VOCREQ; + items[id].vocationString = vocationString; + } else if (identifier == "weaponspecialeffect") { + items[id].weaponSpecialEffect = script.readNumber(); + } else if (identifier == "beddirection") { + items[id].bedPartnerDir = getDirection(script.readIdentifier()); + } else if (identifier == "bedtarget") { + items[id].transformToOnUse = script.readNumber(); + } else if (identifier == "bedfree") { + items[id].transformToFree = script.readNumber(); + } else if (identifier == "weight") { + items[id].weight = script.readNumber(); + } else if (identifier == "rotatetarget") { + items[id].rotateTo = script.readNumber(); + } else if (identifier == "destroytarget") { + items[id].destroyTarget = script.readNumber(); + } else if (identifier == "slottype") { + identifier = asLowerCaseString(script.readIdentifier()); + if (identifier == "head") { + items[id].slotPosition |= SLOTP_HEAD; + } else if (identifier == "body") { + items[id].slotPosition |= SLOTP_ARMOR; + } else if (identifier == "legs") { + items[id].slotPosition |= SLOTP_LEGS; + } else if (identifier == "feet") { + items[id].slotPosition |= SLOTP_FEET; + } else if (identifier == "backpack") { + items[id].slotPosition |= SLOTP_BACKPACK; + } else if (identifier == "twohanded") { + items[id].slotPosition |= SLOTP_TWO_HAND; + } else if (identifier == "righthand") { + items[id].slotPosition &= ~SLOTP_LEFT; + } else if (identifier == "lefthand") { + items[id].slotPosition &= ~SLOTP_RIGHT; + } else if (identifier == "necklace") { + items[id].slotPosition |= SLOTP_NECKLACE; + } else if (identifier == "ring") { + items[id].slotPosition |= SLOTP_RING; + } else if (identifier == "ammo") { + items[id].slotPosition |= SLOTP_AMMO; + } else if (identifier == "hand") { + items[id].slotPosition |= SLOTP_HAND; + } else { + script.error("Unknown slot position"); + return false; + } + } else if (identifier == "speedboost") { + items[id].getAbilities().speed = script.readNumber(); + } else if (identifier == "fistboost") { + items[id].getAbilities().skills[SKILL_FIST] = script.readNumber(); + } else if (identifier == "swordboost") { + items[id].getAbilities().skills[SKILL_SWORD] = script.readNumber(); + } else if (identifier == "clubboost") { + items[id].getAbilities().skills[SKILL_CLUB] = script.readNumber(); + } else if (identifier == "axeboost") { + items[id].getAbilities().skills[SKILL_AXE] = script.readNumber(); + } else if (identifier == "shieldboost") { + items[id].getAbilities().skills[SKILL_SHIELD] = script.readNumber(); + } else if (identifier == "distanceboost") { + items[id].getAbilities().skills[SKILL_DISTANCE] = script.readNumber(); + } else if (identifier == "magicboost") { + items[id].getAbilities().stats[STAT_MAGICPOINTS] = script.readNumber(); + } else if (identifier == "percenthp") { + items[id].getAbilities().statsPercent[STAT_MAXHITPOINTS] = script.readNumber(); + } else if (identifier == "percentmp") { + items[id].getAbilities().statsPercent[STAT_MAXMANAPOINTS] = script.readNumber(); + } else if (identifier == "suppressdrunk") { + if (script.readNumber()) { + items[id].getAbilities().conditionSuppressions |= CONDITION_DRUNK; + } + } else if (identifier == "invisible") { + if (script.readNumber()) { + items[id].getAbilities().invisible = true; + } + } else if (identifier == "manashield") { + if (script.readNumber()) { + items[id].getAbilities().manaShield = true; + } + } else if (identifier == "healthticks") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.healthTicks = script.readNumber(); + } else if (identifier == "healthgain") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.healthGain = script.readNumber(); + } else if (identifier == "manaticks") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.manaTicks = script.readNumber(); + } else if (identifier == "managain") { + Abilities& abilities = items[id].getAbilities(); + abilities.regeneration = true; + abilities.manaGain = script.readNumber(); + } else if (identifier == "absorbmagic") { + int32_t percent = script.readNumber(); + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += percent; + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += percent; + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += percent; + } else if (identifier == "absorbenergy") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbfire") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbpoison") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += script.readNumber(); + } else if (identifier == "absorblifedrain") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += script.readNumber(); + } else if (identifier == "absorbmanadrain") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += script.readNumber(); + } else if (identifier == "absorbphysical") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbhealing") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += script.readNumber(); + } else if (identifier == "absorbundefined") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += script.readNumber(); + } else if (identifier == "absorbfirefield") { + items[id].getAbilities().fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += static_cast(script.readNumber()); + } else if (identifier == "brightness") { + items[id].lightLevel = script.readNumber(); + } else if (identifier == "lightcolor") { + items[id].lightColor = script.readNumber(); + } else if (identifier == "totalexpiretime") { + items[id].decayTime = script.readNumber(); + } else if (identifier == "expiretarget") { + items[id].decayTo = script.readNumber(); + } else if (identifier == "totaluses") { + items[id].charges = script.readNumber(); + } else if (identifier == "weapontype") { + identifier = script.readIdentifier(); + if (identifier == "sword") { + items[id].weaponType = WEAPON_SWORD; + } else if (identifier == "club") { + items[id].weaponType = WEAPON_CLUB; + } else if (identifier == "axe") { + items[id].weaponType = WEAPON_AXE; + } else if (identifier == "shield") { + items[id].weaponType = WEAPON_SHIELD; + } else if (identifier == "distance") { + items[id].weaponType = WEAPON_DISTANCE; + } else if (identifier == "wand") { + items[id].weaponType = WEAPON_WAND; + } else if (identifier == "ammunition") { + items[id].weaponType = WEAPON_AMMO; + } else { + script.error("Unknown weapon type"); + return false; + } + } else if (identifier == "attack") { + items[id].attack = script.readNumber(); + } else if (identifier == "defense") { + items[id].defense = script.readNumber(); + } else if (identifier == "range") { + items[id].shootRange = static_cast(script.readNumber()); + } else if (identifier == "ammotype") { + items[id].ammoType = getAmmoType(script.readIdentifier()); + if (items[id].ammoType == AMMO_NONE) { + script.error("Unknown ammo type"); + return false; + } + } else if (identifier == "missileeffect") { + items[id].shootType = static_cast(script.readNumber()); + } else if (identifier == "fragility") { + items[id].fragility = script.readNumber(); + } else if (identifier == "armorvalue") { + items[id].armor = script.readNumber(); + } else if (identifier == "disguisetarget") { + items[id].disguiseId = script.readNumber(); + } else if (identifier == "equiptarget") { + items[id].transformEquipTo = script.readNumber(); + } else if (identifier == "deequiptarget") { + items[id].transformDeEquipTo = script.readNumber(); + } else { + script.error("Unknown attribute"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + } else if (identifier == "magicfield") { + script.readSymbol('{'); + + CombatType_t combatType = COMBAT_NONE; + ConditionDamage* conditionDamage = nullptr; + + int32_t cycles = 0; + int32_t hit_damage = 0; + + while (true) { + while (true) { + script.nextToken(); + if (script.Token == SPECIAL) { + break; + } + + identifier = script.getIdentifier(); + script.readSymbol('='); + + if (identifier == "type") { + identifier = script.readIdentifier(); + if (identifier == "fire") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + combatType = COMBAT_FIREDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else if (identifier == "energy") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY); + combatType = COMBAT_ENERGYDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else if (identifier == "poison") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + conditionDamage->setParam(CONDITION_PARAM_DELAYED, true); + combatType = COMBAT_EARTHDAMAGE; + items[id].combatType = combatType; + items[id].conditionDamage.reset(conditionDamage); + } else { + script.error("unknown magicfield type"); + return false; + } + } else if (identifier == "count") { + cycles = script.readNumber(); + } else if (identifier == "damage") { + hit_damage = script.readNumber(); + } else { + script.error("unknown identifier"); + return false; + } + } + + if (script.getSpecial() == '}') { + break; + } + + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; + } + } + + int32_t count = 3; + + if (combatType == COMBAT_FIREDAMAGE) { + cycles /= 10; + count = 8; + } else if (combatType == COMBAT_ENERGYDAMAGE) { + cycles /= 20; + count = 10; + } + + conditionDamage->setParam(CONDITION_PARAM_CYCLE, cycles); + conditionDamage->setParam(CONDITION_PARAM_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_MAX_COUNT, count); + conditionDamage->setParam(CONDITION_PARAM_HIT_DAMAGE, hit_damage); + + conditionDamage->setParam(CONDITION_PARAM_FIELD, 1); + } + } + + script.close(); + items.shrink_to_fit(); + + for (ItemType& type : items) { + std::string& name = type.name; + extractArticleAndName(name, type.article, type.name); + nameToItems.insert({ asLowerCaseString(type.name), type.id }); + if (!name.empty()) { + if (type.stackable) { + type.showCount = true; + type.pluralName = pluralizeString(name); + } + } + } + + return true; +} + +ItemType& Items::getItemType(size_t id) +{ + if (id < items.size()) { + return items[id]; + } + return items.front(); +} + +const ItemType& Items::getItemType(size_t id) const +{ + if (id < items.size()) { + return items[id]; + } + return items.front(); +} + +uint16_t Items::getItemIdByName(const std::string& name) +{ + auto result = nameToItems.find(asLowerCaseString(name)); + + if (result == nameToItems.end()) + return 0; + + return result->second; +} diff --git a/src/items.h b/src/items.h new file mode 100644 index 0000000..af2f76e --- /dev/null +++ b/src/items.h @@ -0,0 +1,326 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C +#define FS_ITEMS_H_4E2221634ABA45FE85BA50F710669B3C + +#include "const.h" +#include "enums.h" +#include "position.h" +#include "fileloader.h" + +enum SlotPositionBits : uint32_t { + SLOTP_WHEREEVER = 0xFFFFFFFF, + SLOTP_HEAD = 1 << 0, + SLOTP_NECKLACE = 1 << 1, + SLOTP_BACKPACK = 1 << 2, + SLOTP_ARMOR = 1 << 3, + SLOTP_RIGHT = 1 << 4, + SLOTP_LEFT = 1 << 5, + SLOTP_LEGS = 1 << 6, + SLOTP_FEET = 1 << 7, + SLOTP_RING = 1 << 8, + SLOTP_AMMO = 1 << 9, + SLOTP_DEPOT = 1 << 10, + SLOTP_TWO_HAND = 1 << 11, + SLOTP_HAND = (SLOTP_LEFT | SLOTP_RIGHT) +}; + +enum ItemTypes_t { + ITEM_TYPE_NONE, + ITEM_TYPE_DEPOT, + ITEM_TYPE_MAILBOX, + ITEM_TYPE_CONTAINER, + ITEM_TYPE_DOOR, + ITEM_TYPE_MAGICFIELD, + ITEM_TYPE_TELEPORT, + ITEM_TYPE_BED, + ITEM_TYPE_KEY, + ITEM_TYPE_RUNE, + ITEM_TYPE_CHEST, + ITEM_TYPE_LAST +}; + +enum itemgroup_t { + ITEM_GROUP_NONE, + + ITEM_GROUP_GROUND, + ITEM_GROUP_WEAPON, + ITEM_GROUP_AMMUNITION, + ITEM_GROUP_ARMOR, + ITEM_GROUP_CHARGES, + ITEM_GROUP_TELEPORT, + ITEM_GROUP_MAGICFIELD, + ITEM_GROUP_WRITEABLE, + ITEM_GROUP_KEY, + ITEM_GROUP_SPLASH, + ITEM_GROUP_FLUID, + ITEM_GROUP_DOOR, + ITEM_GROUP_DEPRECATED, + + ITEM_GROUP_LAST +}; + +struct Abilities { + uint32_t healthGain = 0; + uint32_t healthTicks = 0; + uint32_t manaGain = 0; + uint32_t manaTicks = 0; + + uint32_t conditionImmunities = 0; + uint32_t conditionSuppressions = 0; + + //stats modifiers + int32_t stats[STAT_LAST + 1] = { 0 }; + int32_t statsPercent[STAT_LAST + 1] = { 0 }; + + //extra skill modifiers + int32_t skills[SKILL_LAST + 1] = { 0 }; + + int32_t speed = 0; + + // field damage abilities modifiers + int16_t fieldAbsorbPercent[COMBAT_COUNT] = { 0 }; + + //damage abilities modifiers + int16_t absorbPercent[COMBAT_COUNT] = { 0 }; + + bool manaShield = false; + bool invisible = false; + bool regeneration = false; +}; + +class ConditionDamage; + +class ItemType +{ + public: + ItemType() = default; + + //non-copyable + ItemType(const ItemType& other) = delete; + ItemType& operator=(const ItemType& other) = delete; + + ItemType(ItemType&& other) = default; + ItemType& operator=(ItemType&& other) = default; + + bool isGroundTile() const { + return group == ITEM_GROUP_GROUND; + } + bool isContainer() const { + return type == ITEM_TYPE_CONTAINER; + } + bool isChest() const { + return type == ITEM_TYPE_CHEST; + } + bool isSplash() const { + return group == ITEM_GROUP_SPLASH; + } + bool isFluidContainer() const { + return group == ITEM_GROUP_FLUID; + } + + bool isDoor() const { + return (type == ITEM_TYPE_DOOR); + } + bool isMagicField() const { + return (type == ITEM_TYPE_MAGICFIELD); + } + bool isTeleport() const { + return (type == ITEM_TYPE_TELEPORT); + } + bool isKey() const { + return (type == ITEM_TYPE_KEY); + } + bool isDepot() const { + return (type == ITEM_TYPE_DEPOT); + } + bool isMailbox() const { + return (type == ITEM_TYPE_MAILBOX); + } + bool isBed() const { + return (type == ITEM_TYPE_BED); + } + bool isRune() const { + return type == ITEM_TYPE_RUNE; + } + bool hasSubType() const { + return (isFluidContainer() || isSplash() || stackable || charges != 0); + } + + Abilities& getAbilities() { + if (!abilities) { + abilities.reset(new Abilities()); + } + return *abilities; + } + + std::string getPluralName() const { + if (!pluralName.empty()) { + return pluralName; + } + + if (showCount == 0) { + return name; + } + + std::string str; + str.reserve(name.length() + 1); + str.assign(name); + str += 's'; + return str; + } + + itemgroup_t group = ITEM_GROUP_NONE; + ItemTypes_t type = ITEM_TYPE_NONE; + uint16_t id = 0; + bool stackable = false; + + std::string name; + std::string article; + std::string pluralName; + std::string description; + std::string runeSpellName; + std::string vocationString; + + std::unique_ptr abilities; + std::unique_ptr conditionDamage; + + uint32_t weight = 0; + uint32_t decayTime = 0; + uint32_t wieldInfo = 0; + uint32_t minReqLevel = 0; + uint32_t minReqMagicLevel = 0; + uint32_t charges = 0; + int32_t attackStrength = 0; + int32_t attackVariation = 0; + int32_t manaConsumption = 0; + int32_t vocations = 0; + int32_t decayTo = -1; + int32_t attack = 0; + int32_t defense = 0; + int32_t extraDefense = 0; + int32_t armor = 0; + int32_t rotateTo = 0; + int32_t runeMagLevel = 0; + int32_t runeLevel = 0; + int32_t nutrition = 0; + int32_t destroyTarget = 0; + + CombatType_t combatType = COMBAT_NONE; + CombatType_t damageType = COMBAT_NONE; + + uint16_t transformToOnUse = 0; + uint16_t transformToFree = 0; + uint16_t disguiseId = 0; + uint16_t destroyTo = 0; + uint16_t maxTextLen = 0; + uint16_t writeOnceItemId = 0; + uint16_t transformEquipTo = 0; + uint16_t transformDeEquipTo = 0; + uint16_t maxItems = 8; + uint16_t slotPosition = SLOTP_RIGHT | SLOTP_LEFT | SLOTP_AMMO; + uint16_t speed = 0; + + MagicEffectClasses magicEffect = CONST_ME_NONE; + Direction bedPartnerDir = DIRECTION_NONE; + WeaponType_t weaponType = WEAPON_NONE; + Ammo_t ammoType = AMMO_NONE; + ShootType_t shootType = CONST_ANI_NONE; + RaceType_t corpseType = RACE_NONE; + FluidTypes_t fluidSource = FLUID_NONE; + + uint8_t fragility = 0; + uint8_t alwaysOnTopOrder = 0; + uint8_t lightLevel = 0; + uint8_t lightColor = 0; + uint8_t shootRange = 1; + uint8_t weaponSpecialEffect = 0; + + bool collisionEvent = false; + bool separationEvent = false; + bool useEvent = false; + bool multiUseEvent = false; + bool distUse = false; + bool disguise = false; + bool forceUse = false; + bool changeUse = false; + bool destroy = false; + bool corpse = false; + bool hasHeight = false; + bool walkStack = true; + bool blockSolid = false; + bool blockPickupable = false; + bool blockProjectile = false; + bool blockPathFind = false; + bool allowPickupable = true; + bool showDuration = false; + bool showCharges = false; + bool showAttributes = false; + bool replaceable = true; + bool pickupable = false; + bool rotatable = false; + bool useable = false; + bool moveable = true; + bool alwaysOnTop = false; + bool canReadText = false; + bool canWriteText = false; + bool isVertical = false; + bool isHorizontal = false; + bool isHangable = false; + bool allowDistRead = false; + bool lookThrough = false; + bool stopTime = false; + bool showCount = true; +}; + +class Items +{ + public: + using nameMap = std::unordered_multimap; + + Items(); + + // non-copyable + Items(const Items&) = delete; + Items& operator=(const Items&) = delete; + + bool reload(); + void clear(); + + const ItemType& operator[](size_t id) const { + return getItemType(id); + } + const ItemType& getItemType(size_t id) const; + ItemType& getItemType(size_t id); + + uint16_t getItemIdByName(const std::string& name); + + bool loadItems(); + + inline size_t size() const { + return items.size(); + } + + nameMap nameToItems; + + protected: + std::vector items; +}; +#endif diff --git a/src/lockfree.h b/src/lockfree.h new file mode 100644 index 0000000..0b9860c --- /dev/null +++ b/src/lockfree.h @@ -0,0 +1,62 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 +#define FS_LOCKFREE_H_8C707AEB7C7235A2FBC5D4EDDF03B008 + +#if _MSC_FULL_VER >= 190023918 // Workaround for VS2015 Update 2. Boost.Lockfree is a header-only library, so this should be safe to do. +#define _ENABLE_ATOMIC_ALIGNMENT_FIX +#endif + +#include + +template +class LockfreePoolingAllocator : public std::allocator +{ + public: + template + explicit constexpr LockfreePoolingAllocator(const U&) {} + typedef T value_type; + + T* allocate(size_t) const { + T* p; // NOTE: p doesn't have to be initialized + if (!getFreeList().pop(p)) { + //Acquire memory without calling the constructor of T + p = static_cast(operator new (sizeof(T))); + } + return p; + } + + void deallocate(T* p, size_t) const { + if (!getFreeList().bounded_push(p)) { + //Release memory without calling the destructor of T + //(it has already been called at this point) + operator delete(p); + } + } + + private: + typedef boost::lockfree::stack> FreeList; + static FreeList& getFreeList() { + static FreeList freeList; + return freeList; + } +}; + +#endif diff --git a/src/luascript.cpp b/src/luascript.cpp new file mode 100644 index 0000000..231d2b5 --- /dev/null +++ b/src/luascript.cpp @@ -0,0 +1,11417 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "luascript.h" +#include "chat.h" +#include "player.h" +#include "game.h" +#include "protocolstatus.h" +#include "spells.h" +#include "iologindata.h" +#include "configmanager.h" +#include "teleport.h" +#include "databasemanager.h" +#include "bed.h" +#include "monster.h" +#include "scheduler.h" +#include "databasetasks.h" + +extern Chat* g_chat; +extern Game g_game; +extern Monsters g_monsters; +extern ConfigManager g_config; +extern Vocations g_vocations; +extern Spells* g_spells; + +ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; +uint32_t ScriptEnvironment::lastResultId = 0; + +std::multimap ScriptEnvironment::tempItems; + +LuaEnvironment g_luaEnvironment; + +ScriptEnvironment::ScriptEnvironment() +{ + resetEnv(); +} + +ScriptEnvironment::~ScriptEnvironment() +{ + resetEnv(); +} + +void ScriptEnvironment::resetEnv() +{ + scriptId = 0; + callbackId = 0; + timerEvent = false; + interface = nullptr; + localMap.clear(); + tempResults.clear(); + + auto pair = tempItems.equal_range(this); + auto it = pair.first; + while (it != pair.second) { + Item* item = it->second; + if (item->getParent() == VirtualCylinder::virtualCylinder) { + g_game.ReleaseItem(item); + } + it = tempItems.erase(it); + } +} + +bool ScriptEnvironment::setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface) +{ + if (this->callbackId != 0) { + //nested callbacks are not allowed + if (interface) { + interface->reportErrorFunc("Nested callbacks!"); + } + return false; + } + + this->callbackId = callbackId; + interface = scriptInterface; + return true; +} + +void ScriptEnvironment::getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const +{ + scriptId = this->scriptId; + scriptInterface = interface; + callbackId = this->callbackId; + timerEvent = this->timerEvent; +} + +uint32_t ScriptEnvironment::addThing(Thing* thing) +{ + if (!thing || thing->isRemoved()) { + return 0; + } + + Creature* creature = thing->getCreature(); + if (creature) { + return creature->getID(); + } + + Item* item = thing->getItem(); + for (const auto& it : localMap) { + if (it.second == item) { + return it.first; + } + } + + localMap[++lastUID] = item; + return lastUID; +} + +void ScriptEnvironment::insertItem(uint32_t uid, Item* item) +{ + auto result = localMap.emplace(uid, item); + if (!result.second) { + std::cout << std::endl << "Lua Script Error: Thing uid already taken."; + } +} + +Thing* ScriptEnvironment::getThingByUID(uint32_t uid) +{ + if (uid >= 0x10000000) { + return g_game.getCreatureByID(uid); + } + + auto it = localMap.find(uid); + if (it != localMap.end()) { + Item* item = it->second; + if (!item->isRemoved()) { + return item; + } + } + return nullptr; +} + +Item* ScriptEnvironment::getItemByUID(uint32_t uid) +{ + Thing* thing = getThingByUID(uid); + if (!thing) { + return nullptr; + } + return thing->getItem(); +} + +Container* ScriptEnvironment::getContainerByUID(uint32_t uid) +{ + Item* item = getItemByUID(uid); + if (!item) { + return nullptr; + } + return item->getContainer(); +} + +void ScriptEnvironment::removeItemByUID(uint32_t uid) +{ + auto it = localMap.find(uid); + if (it != localMap.end()) { + localMap.erase(it); + } +} + +void ScriptEnvironment::addTempItem(Item* item) +{ + tempItems.emplace(this, item); +} + +void ScriptEnvironment::removeTempItem(Item* item) +{ + for (auto it = tempItems.begin(), end = tempItems.end(); it != end; ++it) { + if (it->second == item) { + tempItems.erase(it); + break; + } + } +} + +uint32_t ScriptEnvironment::addResult(DBResult_ptr res) +{ + tempResults[++lastResultId] = res; + return lastResultId; +} + +bool ScriptEnvironment::removeResult(uint32_t id) +{ + auto it = tempResults.find(id); + if (it == tempResults.end()) { + return false; + } + + tempResults.erase(it); + return true; +} + +DBResult_ptr ScriptEnvironment::getResultByID(uint32_t id) +{ + auto it = tempResults.find(id); + if (it == tempResults.end()) { + return nullptr; + } + return it->second; +} + +std::string LuaScriptInterface::getErrorDesc(ErrorCode_t code) +{ + switch (code) { + case LUA_ERROR_PLAYER_NOT_FOUND: return "Player not found"; + case LUA_ERROR_CREATURE_NOT_FOUND: return "Creature not found"; + case LUA_ERROR_ITEM_NOT_FOUND: return "Item not found"; + case LUA_ERROR_THING_NOT_FOUND: return "Thing not found"; + case LUA_ERROR_TILE_NOT_FOUND: return "Tile not found"; + case LUA_ERROR_HOUSE_NOT_FOUND: return "House not found"; + case LUA_ERROR_COMBAT_NOT_FOUND: return "Combat not found"; + case LUA_ERROR_CONDITION_NOT_FOUND: return "Condition not found"; + case LUA_ERROR_AREA_NOT_FOUND: return "Area not found"; + case LUA_ERROR_CONTAINER_NOT_FOUND: return "Container not found"; + case LUA_ERROR_VARIANT_NOT_FOUND: return "Variant not found"; + case LUA_ERROR_VARIANT_UNKNOWN: return "Unknown variant type"; + case LUA_ERROR_SPELL_NOT_FOUND: return "Spell not found"; + default: return "Bad error code"; + } +} + +ScriptEnvironment LuaScriptInterface::scriptEnv[16]; +int32_t LuaScriptInterface::scriptEnvIndex = -1; + +LuaScriptInterface::LuaScriptInterface(std::string interfaceName) : interfaceName(std::move(interfaceName)) +{ + if (!g_luaEnvironment.getLuaState()) { + g_luaEnvironment.initState(); + } +} + +LuaScriptInterface::~LuaScriptInterface() +{ + closeState(); +} + +bool LuaScriptInterface::reInitState() +{ + g_luaEnvironment.clearCombatObjects(this); + g_luaEnvironment.clearAreaObjects(this); + + closeState(); + return initState(); +} + +/// Same as lua_pcall, but adds stack trace to error strings in called function. +int LuaScriptInterface::protectedCall(lua_State* L, int nargs, int nresults) +{ + int error_index = lua_gettop(L) - nargs; + lua_pushcfunction(L, luaErrorHandler); + lua_insert(L, error_index); + + int ret = lua_pcall(L, nargs, nresults, error_index); + lua_remove(L, error_index); + return ret; +} + +int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = nullptr*/) +{ + //loads file as a chunk at stack top + int ret = luaL_loadfile(luaState, file.c_str()); + if (ret != 0) { + lastLuaError = popString(luaState); + return -1; + } + + //check that it is loaded as a function + if (!isFunction(luaState, -1)) { + return -1; + } + + loadingFile = file; + + if (!reserveScriptEnv()) { + return -1; + } + + ScriptEnvironment* env = getScriptEnv(); + env->setScriptId(EVENT_ID_LOADING, this); + env->setNpc(npc); + + //execute it + ret = protectedCall(luaState, 0, 0); + if (ret != 0) { + reportError(nullptr, popString(luaState)); + resetScriptEnv(); + return -1; + } + + resetScriptEnv(); + return 0; +} + +int32_t LuaScriptInterface::getEvent(const std::string& eventName) +{ + //get our events table + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return -1; + } + + //get current event function pointer + lua_getglobal(luaState, eventName.c_str()); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 2); + return -1; + } + + //save in our events table + lua_pushvalue(luaState, -1); + lua_rawseti(luaState, -3, runningEventId); + lua_pop(luaState, 2); + + //reset global value of this event + lua_pushnil(luaState); + lua_setglobal(luaState, eventName.c_str()); + + cacheFiles[runningEventId] = loadingFile + ":" + eventName; + return runningEventId++; +} + +int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const std::string& eventName) +{ + //get our events table + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return -1; + } + + //get current event function pointer + lua_getglobal(luaState, globalName.c_str()); + lua_getfield(luaState, -1, eventName.c_str()); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 3); + return -1; + } + + //save in our events table + lua_pushvalue(luaState, -1); + lua_rawseti(luaState, -4, runningEventId); + lua_pop(luaState, 1); + + //reset global value of this event + lua_pushnil(luaState); + lua_setfield(luaState, -2, eventName.c_str()); + lua_pop(luaState, 2); + + cacheFiles[runningEventId] = loadingFile + ":" + globalName + "@" + eventName; + return runningEventId++; +} + +const std::string& LuaScriptInterface::getFileById(int32_t scriptId) +{ + if (scriptId == EVENT_ID_LOADING) { + return loadingFile; + } + + auto it = cacheFiles.find(scriptId); + if (it == cacheFiles.end()) { + static const std::string& unk = "(Unknown scriptfile)"; + return unk; + } + return it->second; +} + +std::string LuaScriptInterface::getStackTrace(const std::string& error_desc) +{ + lua_getglobal(luaState, "debug"); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return error_desc; + } + + lua_getfield(luaState, -1, "traceback"); + if (!isFunction(luaState, -1)) { + lua_pop(luaState, 2); + return error_desc; + } + + lua_replace(luaState, -2); + pushString(luaState, error_desc); + lua_call(luaState, 1, 1); + return popString(luaState); +} + +void LuaScriptInterface::reportError(const char* function, const std::string& error_desc, bool stack_trace/* = false*/) +{ + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + LuaScriptInterface* scriptInterface; + getScriptEnv()->getEventInfo(scriptId, scriptInterface, callbackId, timerEvent); + + std::cout << std::endl << "Lua Script Error: "; + + if (scriptInterface) { + std::cout << '[' << scriptInterface->getInterfaceName() << "] " << std::endl; + + if (timerEvent) { + std::cout << "in a timer event called from: " << std::endl; + } + + if (callbackId) { + std::cout << "in callback: " << scriptInterface->getFileById(callbackId) << std::endl; + } + + std::cout << scriptInterface->getFileById(scriptId) << std::endl; + } + + if (function) { + std::cout << function << "(). "; + } + + if (stack_trace && scriptInterface) { + std::cout << scriptInterface->getStackTrace(error_desc) << std::endl; + } else { + std::cout << error_desc << std::endl; + } +} + +bool LuaScriptInterface::pushFunction(int32_t functionId) +{ + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + return false; + } + + lua_rawgeti(luaState, -1, functionId); + lua_replace(luaState, -2); + return isFunction(luaState, -1); +} + +bool LuaScriptInterface::initState() +{ + luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return false; + } + + lua_newtable(luaState); + eventTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + runningEventId = EVENT_ID_USER; + return true; +} + +bool LuaScriptInterface::closeState() +{ + if (!g_luaEnvironment.getLuaState() || !luaState) { + return false; + } + + cacheFiles.clear(); + if (eventTableRef != -1) { + luaL_unref(luaState, LUA_REGISTRYINDEX, eventTableRef); + eventTableRef = -1; + } + + luaState = nullptr; + return true; +} + +int LuaScriptInterface::luaErrorHandler(lua_State* L) +{ + const std::string& errorMessage = popString(L); + auto interface = getScriptEnv()->getScriptInterface(); + assert(interface); //This fires if the ScriptEnvironment hasn't been setup + pushString(L, interface->getStackTrace(errorMessage)); + return 1; +} + +bool LuaScriptInterface::callFunction(int params) +{ + bool result = false; + int size = lua_gettop(luaState); + if (protectedCall(luaState, params, 1) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::getString(luaState, -1)); + } else { + result = LuaScriptInterface::getBoolean(luaState, -1); + } + + lua_pop(luaState, 1); + if ((lua_gettop(luaState) + params + 1) != size) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + resetScriptEnv(); + return result; +} + +void LuaScriptInterface::callVoidFunction(int params) +{ + int size = lua_gettop(luaState); + if (protectedCall(luaState, params, 0) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(luaState)); + } + + if ((lua_gettop(luaState) + params + 1) != size) { + LuaScriptInterface::reportError(nullptr, "Stack size changed!"); + } + + resetScriptEnv(); +} + +void LuaScriptInterface::pushVariant(lua_State* L, const LuaVariant& var) +{ + lua_createtable(L, 0, 2); + setField(L, "type", var.type); + switch (var.type) { + case VARIANT_NUMBER: + setField(L, "number", var.number); + break; + case VARIANT_STRING: + setField(L, "string", var.text); + break; + case VARIANT_TARGETPOSITION: + case VARIANT_POSITION: { + pushPosition(L, var.pos); + lua_setfield(L, -2, "pos"); + break; + } + default: + break; + } + setMetatable(L, -1, "Variant"); +} + +void LuaScriptInterface::pushThing(lua_State* L, Thing* thing) +{ + if (!thing) { + lua_createtable(L, 0, 4); + setField(L, "uid", 0); + setField(L, "itemid", 0); + setField(L, "actionid", 0); + setField(L, "type", 0); + return; + } + + if (Item* item = thing->getItem()) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else if (Creature* creature = thing->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } +} + +void LuaScriptInterface::pushCylinder(lua_State* L, Cylinder* cylinder) +{ + if (Creature* creature = cylinder->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else if (Item* parentItem = cylinder->getItem()) { + pushUserdata(L, parentItem); + setItemMetatable(L, -1, parentItem); + } else if (Tile* tile = cylinder->getTile()) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else if (cylinder == VirtualCylinder::virtualCylinder) { + pushBoolean(L, true); + } else { + lua_pushnil(L); + } +} + +void LuaScriptInterface::pushString(lua_State* L, const std::string& value) +{ + lua_pushlstring(L, value.c_str(), value.length()); +} + +void LuaScriptInterface::pushCallback(lua_State* L, int32_t callback) +{ + lua_rawgeti(L, LUA_REGISTRYINDEX, callback); +} + +std::string LuaScriptInterface::popString(lua_State* L) +{ + if (lua_gettop(L) == 0) { + return std::string(); + } + + std::string str(getString(L, -1)); + lua_pop(L, 1); + return str; +} + +int32_t LuaScriptInterface::popCallback(lua_State* L) +{ + return luaL_ref(L, LUA_REGISTRYINDEX); +} + +// Metatables +void LuaScriptInterface::setMetatable(lua_State* L, int32_t index, const std::string& name) +{ + luaL_getmetatable(L, name.c_str()); + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setWeakMetatable(lua_State* L, int32_t index, const std::string& name) +{ + static std::set weakObjectTypes; + const std::string& weakName = name + "_weak"; + + auto result = weakObjectTypes.emplace(name); + if (result.second) { + luaL_getmetatable(L, name.c_str()); + int childMetatable = lua_gettop(L); + + luaL_newmetatable(L, weakName.c_str()); + int metatable = lua_gettop(L); + + static const std::vector methodKeys = {"__index", "__metatable", "__eq"}; + for (const std::string& metaKey : methodKeys) { + lua_getfield(L, childMetatable, metaKey.c_str()); + lua_setfield(L, metatable, metaKey.c_str()); + } + + static const std::vector methodIndexes = {'h', 'p', 't'}; + for (int metaIndex : methodIndexes) { + lua_rawgeti(L, childMetatable, metaIndex); + lua_rawseti(L, metatable, metaIndex); + } + + lua_pushnil(L); + lua_setfield(L, metatable, "__gc"); + + lua_remove(L, childMetatable); + } else { + luaL_getmetatable(L, weakName.c_str()); + } + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setItemMetatable(lua_State* L, int32_t index, const Item* item) +{ + if (item->getContainer()) { + luaL_getmetatable(L, "Container"); + } else if (item->getTeleport()) { + luaL_getmetatable(L, "Teleport"); + } else { + luaL_getmetatable(L, "Item"); + } + lua_setmetatable(L, index - 1); +} + +void LuaScriptInterface::setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature) +{ + if (creature->getPlayer()) { + luaL_getmetatable(L, "Player"); + } else if (creature->getMonster()) { + luaL_getmetatable(L, "Monster"); + } else { + luaL_getmetatable(L, "Npc"); + } + lua_setmetatable(L, index - 1); +} + +// Get +std::string LuaScriptInterface::getString(lua_State* L, int32_t arg) +{ + size_t len; + const char* c_str = lua_tolstring(L, arg, &len); + if (!c_str || len == 0) { + return std::string(); + } + return std::string(c_str, len); +} + +Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg, int32_t& stackpos) +{ + Position position; + position.x = getField(L, arg, "x"); + position.y = getField(L, arg, "y"); + position.z = getField(L, arg, "z"); + + lua_getfield(L, arg, "stackpos"); + if (lua_isnil(L, -1) == 1) { + stackpos = 0; + } else { + stackpos = getNumber(L, -1); + } + + lua_pop(L, 4); + return position; +} + +Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) +{ + Position position; + position.x = getField(L, arg, "x"); + position.y = getField(L, arg, "y"); + position.z = getField(L, arg, "z"); + + lua_pop(L, 3); + return position; +} + +Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) +{ + Outfit_t outfit; + outfit.lookFeet = getField(L, arg, "lookFeet"); + outfit.lookLegs = getField(L, arg, "lookLegs"); + outfit.lookBody = getField(L, arg, "lookBody"); + outfit.lookHead = getField(L, arg, "lookHead"); + + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookType = getField(L, arg, "lookType"); + + lua_pop(L, 6); + return outfit; +} + +LuaVariant LuaScriptInterface::getVariant(lua_State* L, int32_t arg) +{ + LuaVariant var; + switch (var.type = getField(L, arg, "type")) { + case VARIANT_NUMBER: { + var.number = getField(L, arg, "number"); + lua_pop(L, 2); + break; + } + + case VARIANT_STRING: { + var.text = getFieldString(L, arg, "string"); + lua_pop(L, 2); + break; + } + + case VARIANT_POSITION: + case VARIANT_TARGETPOSITION: { + lua_getfield(L, arg, "pos"); + var.pos = getPosition(L, lua_gettop(L)); + lua_pop(L, 2); + break; + } + + default: { + var.type = VARIANT_NONE; + lua_pop(L, 1); + break; + } + } + return var; +} + +Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) +{ + Thing* thing; + if (lua_getmetatable(L, arg) != 0) { + lua_rawgeti(L, -1, 't'); + switch(getNumber(L, -1)) { + case LuaData_Item: + thing = getUserdata(L, arg); + break; + case LuaData_Container: + thing = getUserdata(L, arg); + break; + case LuaData_Teleport: + thing = getUserdata(L, arg); + break; + case LuaData_Player: + thing = getUserdata(L, arg); + break; + case LuaData_Monster: + thing = getUserdata(L, arg); + break; + case LuaData_Npc: + thing = getUserdata(L, arg); + break; + default: + thing = nullptr; + break; + } + lua_pop(L, 2); + } else { + thing = getScriptEnv()->getThingByUID(getNumber(L, arg)); + } + return thing; +} + +Creature* LuaScriptInterface::getCreature(lua_State* L, int32_t arg) +{ + if (isUserdata(L, arg)) { + return getUserdata(L, arg); + } + return g_game.getCreatureByID(getNumber(L, arg)); +} + +Player* LuaScriptInterface::getPlayer(lua_State* L, int32_t arg) +{ + if (isUserdata(L, arg)) { + return getUserdata(L, arg); + } + return g_game.getPlayerByID(getNumber(L, arg)); +} + +std::string LuaScriptInterface::getFieldString(lua_State* L, int32_t arg, const std::string& key) +{ + lua_getfield(L, arg, key.c_str()); + return getString(L, -1); +} + +LuaDataType LuaScriptInterface::getUserdataType(lua_State* L, int32_t arg) +{ + if (lua_getmetatable(L, arg) == 0) { + return LuaData_Unknown; + } + lua_rawgeti(L, -1, 't'); + + LuaDataType type = getNumber(L, -1); + lua_pop(L, 2); + + return type; +} + +// Push +void LuaScriptInterface::pushBoolean(lua_State* L, bool value) +{ + lua_pushboolean(L, value ? 1 : 0); +} + +void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos/* = 0*/) +{ + lua_createtable(L, 0, 4); + + setField(L, "x", position.x); + setField(L, "y", position.y); + setField(L, "z", position.z); + setField(L, "stackpos", stackpos); + + setMetatable(L, -1, "Position"); +} + +void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) +{ + lua_createtable(L, 0, 6); + setField(L, "lookType", outfit.lookType); + setField(L, "lookTypeEx", outfit.lookTypeEx); + setField(L, "lookHead", outfit.lookHead); + setField(L, "lookBody", outfit.lookBody); + setField(L, "lookLegs", outfit.lookLegs); + setField(L, "lookFeet", outfit.lookFeet); +} + +#define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } +#define registerEnumIn(tableName, value) { std::string enumName = #value; registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); } + +void LuaScriptInterface::registerFunctions() +{ + //getPlayerFlagValue(cid, flag) + lua_register(luaState, "getPlayerFlagValue", LuaScriptInterface::luaGetPlayerFlagValue); + + //getPlayerInstantSpellCount(cid) + lua_register(luaState, "getPlayerInstantSpellCount", LuaScriptInterface::luaGetPlayerInstantSpellCount); + + //getPlayerInstantSpellInfo(cid, index) + lua_register(luaState, "getPlayerInstantSpellInfo", LuaScriptInterface::luaGetPlayerInstantSpellInfo); + + //doPlayerAddItem(uid, itemid, count/subtype) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + //Returns uid of the created item + lua_register(luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); + + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + lua_register(luaState, "doCreateItem", LuaScriptInterface::luaDoCreateItem); + + //doCreateItemEx(itemid, count/subtype) + lua_register(luaState, "doCreateItemEx", LuaScriptInterface::luaDoCreateItemEx); + + //doTileAddItemEx(pos, uid) + lua_register(luaState, "doTileAddItemEx", LuaScriptInterface::luaDoTileAddItemEx); + + //doMoveCreature(cid, direction) + lua_register(luaState, "doMoveCreature", LuaScriptInterface::luaDoMoveCreature); + + //doSetCreatureLight(cid, lightLevel, lightColor, time) + lua_register(luaState, "doSetCreatureLight", LuaScriptInterface::luaDoSetCreatureLight); + + //getCreatureCondition(cid, condition[, subId]) + lua_register(luaState, "getCreatureCondition", LuaScriptInterface::luaGetCreatureCondition); + + //isValidUID(uid) + lua_register(luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); + + //isDepot(uid) + lua_register(luaState, "isDepot", LuaScriptInterface::luaIsDepot); + + //isMovable(uid) + lua_register(luaState, "isMovable", LuaScriptInterface::luaIsMoveable); + + //doAddContainerItem(uid, itemid, count/subtype) + lua_register(luaState, "doAddContainerItem", LuaScriptInterface::luaDoAddContainerItem); + + //getDepotId(uid) + lua_register(luaState, "getDepotId", LuaScriptInterface::luaGetDepotId); + + //getWorldTime() + lua_register(luaState, "getWorldTime", LuaScriptInterface::luaGetWorldTime); + + //getWorldLight() + lua_register(luaState, "getWorldLight", LuaScriptInterface::luaGetWorldLight); + + //getWorldUpTime() + lua_register(luaState, "getWorldUpTime", LuaScriptInterface::luaGetWorldUpTime); + + //createCombatArea( {area}, {extArea} ) + lua_register(luaState, "createCombatArea", LuaScriptInterface::luaCreateCombatArea); + + //doAreaCombatHealth(cid, type, pos, area, min, max, effect) + lua_register(luaState, "doAreaCombatHealth", LuaScriptInterface::luaDoAreaCombatHealth); + + //doTargetCombatHealth(cid, target, type, min, max, effect) + lua_register(luaState, "doTargetCombatHealth", LuaScriptInterface::luaDoTargetCombatHealth); + + //doAreaCombatMana(cid, pos, area, min, max, effect) + lua_register(luaState, "doAreaCombatMana", LuaScriptInterface::luaDoAreaCombatMana); + + //doTargetCombatMana(cid, target, min, max, effect) + lua_register(luaState, "doTargetCombatMana", LuaScriptInterface::luaDoTargetCombatMana); + + //doAreaCombatCondition(cid, pos, area, condition, effect) + lua_register(luaState, "doAreaCombatCondition", LuaScriptInterface::luaDoAreaCombatCondition); + + //doTargetCombatCondition(cid, target, condition, effect) + lua_register(luaState, "doTargetCombatCondition", LuaScriptInterface::luaDoTargetCombatCondition); + + //doAreaCombatDispel(cid, pos, area, type, effect) + lua_register(luaState, "doAreaCombatDispel", LuaScriptInterface::luaDoAreaCombatDispel); + + //doTargetCombatDispel(cid, target, type, effect) + lua_register(luaState, "doTargetCombatDispel", LuaScriptInterface::luaDoTargetCombatDispel); + + //doChallengeCreature(cid, target) + lua_register(luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); + + //doSetMonsterOutfit(cid, name, time) + lua_register(luaState, "doSetMonsterOutfit", LuaScriptInterface::luaSetMonsterOutfit); + + //doSetItemOutfit(cid, item, time) + lua_register(luaState, "doSetItemOutfit", LuaScriptInterface::luaSetItemOutfit); + + //doSetCreatureOutfit(cid, outfit, time) + lua_register(luaState, "doSetCreatureOutfit", LuaScriptInterface::luaSetCreatureOutfit); + + //isInArray(array, value) + lua_register(luaState, "isInArray", LuaScriptInterface::luaIsInArray); + + //addEvent(callback, delay, ...) + lua_register(luaState, "addEvent", LuaScriptInterface::luaAddEvent); + + //stopEvent(eventid) + lua_register(luaState, "stopEvent", LuaScriptInterface::luaStopEvent); + + //saveServer() + lua_register(luaState, "saveServer", LuaScriptInterface::luaSaveServer); + + //cleanMap() + lua_register(luaState, "cleanMap", LuaScriptInterface::luaCleanMap); + + //debugPrint(text) + lua_register(luaState, "debugPrint", LuaScriptInterface::luaDebugPrint); + + //isInWar(cid, target) + lua_register(luaState, "isInWar", LuaScriptInterface::luaIsInWar); + + //getWaypointPosition(name) + lua_register(luaState, "getWaypointPositionByName", LuaScriptInterface::luaGetWaypointPositionByName); + +#ifndef LUAJIT_VERSION + //bit operations for Lua, based on bitlib project release 24 + //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + luaL_register(luaState, "bit", LuaScriptInterface::luaBitReg); +#endif + + //configManager table + luaL_register(luaState, "configManager", LuaScriptInterface::luaConfigManagerTable); + + //db table + luaL_register(luaState, "db", LuaScriptInterface::luaDatabaseTable); + + //result table + luaL_register(luaState, "result", LuaScriptInterface::luaResultTable); + + /* New functions */ + //registerClass(className, baseClass, newFunction) + //registerTable(tableName) + //registerMethod(className, functionName, function) + //registerMetaMethod(className, functionName, function) + //registerGlobalMethod(functionName, function) + //registerVariable(tableName, name, value) + //registerGlobalVariable(name, value) + //registerEnum(value) + //registerEnumIn(tableName, value) + + // Enums + registerEnum(ACCOUNT_TYPE_NORMAL) + registerEnum(ACCOUNT_TYPE_TUTOR) + registerEnum(ACCOUNT_TYPE_SENIORTUTOR) + registerEnum(ACCOUNT_TYPE_GAMEMASTER) + registerEnum(ACCOUNT_TYPE_GOD) + + registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE) + registerEnum(CALLBACK_PARAM_SKILLVALUE) + registerEnum(CALLBACK_PARAM_TARGETTILE) + registerEnum(CALLBACK_PARAM_TARGETCREATURE) + + registerEnum(COMBAT_FORMULA_UNDEFINED) + registerEnum(COMBAT_FORMULA_LEVELMAGIC) + registerEnum(COMBAT_FORMULA_SKILL) + registerEnum(COMBAT_FORMULA_DAMAGE) + + registerEnum(DIRECTION_NORTH) + registerEnum(DIRECTION_EAST) + registerEnum(DIRECTION_SOUTH) + registerEnum(DIRECTION_WEST) + registerEnum(DIRECTION_SOUTHWEST) + registerEnum(DIRECTION_SOUTHEAST) + registerEnum(DIRECTION_NORTHWEST) + registerEnum(DIRECTION_NORTHEAST) + + registerEnum(COMBAT_NONE) + registerEnum(COMBAT_PHYSICALDAMAGE) + registerEnum(COMBAT_ENERGYDAMAGE) + registerEnum(COMBAT_EARTHDAMAGE) + registerEnum(COMBAT_FIREDAMAGE) + registerEnum(COMBAT_UNDEFINEDDAMAGE) + registerEnum(COMBAT_LIFEDRAIN) + registerEnum(COMBAT_MANADRAIN) + registerEnum(COMBAT_HEALING) + + registerEnum(COMBAT_PARAM_TYPE) + registerEnum(COMBAT_PARAM_EFFECT) + registerEnum(COMBAT_PARAM_DISTANCEEFFECT) + registerEnum(COMBAT_PARAM_BLOCKSHIELD) + registerEnum(COMBAT_PARAM_BLOCKARMOR) + registerEnum(COMBAT_PARAM_TARGETCASTERORTOPMOST) + registerEnum(COMBAT_PARAM_CREATEITEM) + registerEnum(COMBAT_PARAM_AGGRESSIVE) + registerEnum(COMBAT_PARAM_DISPEL) + registerEnum(COMBAT_PARAM_USECHARGES) + registerEnum(COMBAT_PARAM_DECREASEDAMAGE) + registerEnum(COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE) + + registerEnum(CONDITION_NONE) + registerEnum(CONDITION_POISON) + registerEnum(CONDITION_FIRE) + registerEnum(CONDITION_ENERGY) + registerEnum(CONDITION_HASTE) + registerEnum(CONDITION_PARALYZE) + registerEnum(CONDITION_OUTFIT) + registerEnum(CONDITION_INVISIBLE) + registerEnum(CONDITION_LIGHT) + registerEnum(CONDITION_MANASHIELD) + registerEnum(CONDITION_INFIGHT) + registerEnum(CONDITION_DRUNK) + registerEnum(CONDITION_REGENERATION) + registerEnum(CONDITION_SOUL) + registerEnum(CONDITION_MUTED) + registerEnum(CONDITION_CHANNELMUTEDTICKS) + registerEnum(CONDITION_YELLTICKS) + registerEnum(CONDITION_ATTRIBUTES) + registerEnum(CONDITION_EXHAUST) + registerEnum(CONDITION_PACIFIED) + + registerEnum(CONDITIONID_DEFAULT) + registerEnum(CONDITIONID_COMBAT) + registerEnum(CONDITIONID_HEAD) + registerEnum(CONDITIONID_NECKLACE) + registerEnum(CONDITIONID_BACKPACK) + registerEnum(CONDITIONID_ARMOR) + registerEnum(CONDITIONID_RIGHT) + registerEnum(CONDITIONID_LEFT) + registerEnum(CONDITIONID_LEGS) + registerEnum(CONDITIONID_FEET) + registerEnum(CONDITIONID_RING) + registerEnum(CONDITIONID_AMMO) + + registerEnum(CONDITION_PARAM_OWNER) + registerEnum(CONDITION_PARAM_TICKS) + registerEnum(CONDITION_PARAM_HEALTHGAIN) + registerEnum(CONDITION_PARAM_HEALTHTICKS) + registerEnum(CONDITION_PARAM_MANAGAIN) + registerEnum(CONDITION_PARAM_MANATICKS) + registerEnum(CONDITION_PARAM_DELAYED) + registerEnum(CONDITION_PARAM_SPEED) + registerEnum(CONDITION_PARAM_LIGHT_LEVEL) + registerEnum(CONDITION_PARAM_LIGHT_COLOR) + registerEnum(CONDITION_PARAM_SOULGAIN) + registerEnum(CONDITION_PARAM_SOULTICKS) + registerEnum(CONDITION_PARAM_MINVALUE) + registerEnum(CONDITION_PARAM_MAXVALUE) + registerEnum(CONDITION_PARAM_STARTVALUE) + registerEnum(CONDITION_PARAM_TICKINTERVAL) + registerEnum(CONDITION_PARAM_SKILL_MELEE) + registerEnum(CONDITION_PARAM_SKILL_FIST) + registerEnum(CONDITION_PARAM_SKILL_CLUB) + registerEnum(CONDITION_PARAM_SKILL_SWORD) + registerEnum(CONDITION_PARAM_SKILL_AXE) + registerEnum(CONDITION_PARAM_SKILL_DISTANCE) + registerEnum(CONDITION_PARAM_SKILL_SHIELD) + registerEnum(CONDITION_PARAM_SKILL_FISHING) + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTS) + registerEnum(CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT) + registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT) + registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT) + registerEnum(CONDITION_PARAM_PERIODICDAMAGE) + registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT) + registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT) + registerEnum(CONDITION_PARAM_SKILL_SWORDPERCENT) + registerEnum(CONDITION_PARAM_SKILL_AXEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT) + registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT) + registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT) + registerEnum(CONDITION_PARAM_SUBID) + registerEnum(CONDITION_PARAM_FIELD) + + registerEnum(CONST_ME_NONE) + registerEnum(CONST_ME_DRAWBLOOD) + registerEnum(CONST_ME_LOSEENERGY) + registerEnum(CONST_ME_POFF) + registerEnum(CONST_ME_BLOCKHIT) + registerEnum(CONST_ME_EXPLOSIONAREA) + registerEnum(CONST_ME_EXPLOSIONHIT) + registerEnum(CONST_ME_FIREAREA) + registerEnum(CONST_ME_YELLOW_RINGS) + registerEnum(CONST_ME_GREEN_RINGS) + registerEnum(CONST_ME_HITAREA) + registerEnum(CONST_ME_TELEPORT) + registerEnum(CONST_ME_ENERGYHIT) + registerEnum(CONST_ME_MAGIC_BLUE) + registerEnum(CONST_ME_MAGIC_RED) + registerEnum(CONST_ME_MAGIC_GREEN) + registerEnum(CONST_ME_HITBYFIRE) + registerEnum(CONST_ME_HITBYPOISON) + registerEnum(CONST_ME_MORTAREA) + registerEnum(CONST_ME_SOUND_GREEN) + registerEnum(CONST_ME_SOUND_RED) + registerEnum(CONST_ME_POISONAREA) + registerEnum(CONST_ME_SOUND_YELLOW) + registerEnum(CONST_ME_SOUND_PURPLE) + registerEnum(CONST_ME_SOUND_BLUE) + registerEnum(CONST_ME_SOUND_WHITE) + registerEnum(CONST_ANI_NONE) + registerEnum(CONST_ANI_SPEAR) + registerEnum(CONST_ANI_BOLT) + registerEnum(CONST_ANI_ARROW) + registerEnum(CONST_ANI_FIRE) + registerEnum(CONST_ANI_ENERGY) + registerEnum(CONST_ANI_POISONARROW) + registerEnum(CONST_ANI_BURSTARROW) + registerEnum(CONST_ANI_THROWINGSTAR) + registerEnum(CONST_ANI_THROWINGKNIFE) + registerEnum(CONST_ANI_SMALLSTONE) + registerEnum(CONST_ANI_DEATH) + registerEnum(CONST_ANI_LARGEROCK) + registerEnum(CONST_ANI_SNOWBALL) + registerEnum(CONST_ANI_POWERBOLT) + registerEnum(CONST_ANI_POISON) + + registerEnum(CONST_PROP_BLOCKSOLID) + registerEnum(CONST_PROP_HASHEIGHT) + registerEnum(CONST_PROP_BLOCKPROJECTILE) + registerEnum(CONST_PROP_BLOCKPATH) + registerEnum(CONST_PROP_ISVERTICAL) + registerEnum(CONST_PROP_ISHORIZONTAL) + registerEnum(CONST_PROP_MOVEABLE) + registerEnum(CONST_PROP_IMMOVABLEBLOCKSOLID) + registerEnum(CONST_PROP_IMMOVABLEBLOCKPATH) + registerEnum(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) + registerEnum(CONST_PROP_NOFIELDBLOCKPATH) + registerEnum(CONST_PROP_SUPPORTHANGABLE) + + registerEnum(CONST_SLOT_HEAD) + registerEnum(CONST_SLOT_NECKLACE) + registerEnum(CONST_SLOT_BACKPACK) + registerEnum(CONST_SLOT_ARMOR) + registerEnum(CONST_SLOT_RIGHT) + registerEnum(CONST_SLOT_LEFT) + registerEnum(CONST_SLOT_LEGS) + registerEnum(CONST_SLOT_FEET) + registerEnum(CONST_SLOT_RING) + registerEnum(CONST_SLOT_AMMO) + + registerEnum(CREATURE_EVENT_NONE) + registerEnum(CREATURE_EVENT_LOGIN) + registerEnum(CREATURE_EVENT_LOGOUT) + registerEnum(CREATURE_EVENT_THINK) + registerEnum(CREATURE_EVENT_PREPAREDEATH) + registerEnum(CREATURE_EVENT_DEATH) + registerEnum(CREATURE_EVENT_KILL) + registerEnum(CREATURE_EVENT_ADVANCE) + registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) + + registerEnum(GAME_STATE_STARTUP) + registerEnum(GAME_STATE_INIT) + registerEnum(GAME_STATE_NORMAL) + registerEnum(GAME_STATE_CLOSED) + registerEnum(GAME_STATE_SHUTDOWN) + registerEnum(GAME_STATE_CLOSING) + registerEnum(GAME_STATE_MAINTAIN) + + registerEnum(MESSAGE_STATUS_CONSOLE_BLUE) + registerEnum(MESSAGE_STATUS_CONSOLE_RED) + registerEnum(MESSAGE_STATUS_DEFAULT) + registerEnum(MESSAGE_STATUS_WARNING) + registerEnum(MESSAGE_EVENT_ADVANCE) + registerEnum(MESSAGE_STATUS_SMALL) + registerEnum(MESSAGE_INFO_DESCR) + registerEnum(MESSAGE_EVENT_DEFAULT) + registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) + + registerEnum(CLIENTOS_LINUX) + registerEnum(CLIENTOS_WINDOWS) + registerEnum(CLIENTOS_FLASH) + registerEnum(CLIENTOS_OTCLIENT_LINUX) + registerEnum(CLIENTOS_OTCLIENT_WINDOWS) + registerEnum(CLIENTOS_OTCLIENT_MAC) + + registerEnum(ITEM_ATTRIBUTE_NONE) + registerEnum(ITEM_ATTRIBUTE_ACTIONID) + registerEnum(ITEM_ATTRIBUTE_MOVEMENTID) + registerEnum(ITEM_ATTRIBUTE_DESCRIPTION) + registerEnum(ITEM_ATTRIBUTE_TEXT) + registerEnum(ITEM_ATTRIBUTE_DATE) + registerEnum(ITEM_ATTRIBUTE_WRITER) + registerEnum(ITEM_ATTRIBUTE_NAME) + registerEnum(ITEM_ATTRIBUTE_ARTICLE) + registerEnum(ITEM_ATTRIBUTE_PLURALNAME) + registerEnum(ITEM_ATTRIBUTE_WEIGHT) + registerEnum(ITEM_ATTRIBUTE_ATTACK) + registerEnum(ITEM_ATTRIBUTE_DEFENSE) + registerEnum(ITEM_ATTRIBUTE_ARMOR) + registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE) + registerEnum(ITEM_ATTRIBUTE_OWNER) + registerEnum(ITEM_ATTRIBUTE_DURATION) + registerEnum(ITEM_ATTRIBUTE_DECAYSTATE) + registerEnum(ITEM_ATTRIBUTE_CORPSEOWNER) + registerEnum(ITEM_ATTRIBUTE_CHARGES) + registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE) + registerEnum(ITEM_ATTRIBUTE_DOORID) + registerEnum(ITEM_ATTRIBUTE_KEYNUMBER) + registerEnum(ITEM_ATTRIBUTE_KEYHOLENUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTNUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTVALUE) + registerEnum(ITEM_ATTRIBUTE_DOORLEVEL) + registerEnum(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) + + registerEnum(ITEM_TYPE_DEPOT) + registerEnum(ITEM_TYPE_MAILBOX) + registerEnum(ITEM_TYPE_CONTAINER) + registerEnum(ITEM_TYPE_DOOR) + registerEnum(ITEM_TYPE_MAGICFIELD) + registerEnum(ITEM_TYPE_TELEPORT) + registerEnum(ITEM_TYPE_BED) + registerEnum(ITEM_TYPE_KEY) + registerEnum(ITEM_TYPE_RUNE) + registerEnum(ITEM_TYPE_CHEST) + + registerEnum(ITEM_GOLD_COIN) + registerEnum(ITEM_PLATINUM_COIN) + registerEnum(ITEM_CRYSTAL_COIN) + registerEnum(ITEM_AMULETOFLOSS) + registerEnum(ITEM_PARCEL) + registerEnum(ITEM_LABEL) + registerEnum(ITEM_FIREFIELD_PVP_FULL) + registerEnum(ITEM_FIREFIELD_PVP_MEDIUM) + registerEnum(ITEM_FIREFIELD_PVP_SMALL) + registerEnum(ITEM_FIREFIELD_PERSISTENT_FULL) + registerEnum(ITEM_FIREFIELD_PERSISTENT_MEDIUM) + registerEnum(ITEM_FIREFIELD_PERSISTENT_SMALL) + registerEnum(ITEM_FIREFIELD_NOPVP) + registerEnum(ITEM_POISONFIELD_PVP) + registerEnum(ITEM_POISONFIELD_PERSISTENT) + registerEnum(ITEM_POISONFIELD_NOPVP) + registerEnum(ITEM_ENERGYFIELD_PVP) + registerEnum(ITEM_ENERGYFIELD_PERSISTENT) + registerEnum(ITEM_ENERGYFIELD_NOPVP) + registerEnum(ITEM_MAGICWALL) + registerEnum(ITEM_MAGICWALL_PERSISTENT) + registerEnum(ITEM_WILDGROWTH) + registerEnum(ITEM_WILDGROWTH_PERSISTENT) + + registerEnum(PlayerFlag_CannotUseCombat) + registerEnum(PlayerFlag_CannotAttackPlayer) + registerEnum(PlayerFlag_CannotAttackMonster) + registerEnum(PlayerFlag_CannotBeAttacked) + registerEnum(PlayerFlag_CanConvinceAll) + registerEnum(PlayerFlag_CanSummonAll) + registerEnum(PlayerFlag_CanIllusionAll) + registerEnum(PlayerFlag_CanSenseInvisibility) + registerEnum(PlayerFlag_IgnoredByMonsters) + registerEnum(PlayerFlag_NotGainInFight) + registerEnum(PlayerFlag_HasInfiniteMana) + registerEnum(PlayerFlag_HasInfiniteSoul) + registerEnum(PlayerFlag_HasNoExhaustion) + registerEnum(PlayerFlag_CannotUseSpells) + registerEnum(PlayerFlag_CannotPickupItem) + registerEnum(PlayerFlag_CanAlwaysLogin) + registerEnum(PlayerFlag_CanBroadcast) + registerEnum(PlayerFlag_CanEditHouses) + registerEnum(PlayerFlag_CannotBeBanned) + registerEnum(PlayerFlag_CannotBePushed) + registerEnum(PlayerFlag_HasInfiniteCapacity) + registerEnum(PlayerFlag_CanPushAllCreatures) + registerEnum(PlayerFlag_CanTalkRedPrivate) + registerEnum(PlayerFlag_CanTalkRedChannel) + registerEnum(PlayerFlag_TalkOrangeHelpChannel) + registerEnum(PlayerFlag_NotGainExperience) + registerEnum(PlayerFlag_NotGainMana) + registerEnum(PlayerFlag_NotGainHealth) + registerEnum(PlayerFlag_NotGainSkill) + registerEnum(PlayerFlag_SetMaxSpeed) + registerEnum(PlayerFlag_SpecialVIP) + registerEnum(PlayerFlag_NotGenerateLoot) + registerEnum(PlayerFlag_CanTalkRedChannelAnonymous) + registerEnum(PlayerFlag_IgnoreProtectionZone) + registerEnum(PlayerFlag_IgnoreSpellCheck) + registerEnum(PlayerFlag_IgnoreWeaponCheck) + registerEnum(PlayerFlag_CannotBeMuted) + registerEnum(PlayerFlag_IsAlwaysPremium) + registerEnum(PlayerFlag_SpecialMoveUse) + + registerEnum(PLAYERSEX_FEMALE) + registerEnum(PLAYERSEX_MALE) + + registerEnum(VOCATION_NONE) + + registerEnum(FIGHTMODE_ATTACK) + registerEnum(FIGHTMODE_BALANCED) + registerEnum(FIGHTMODE_DEFENSE) + + registerEnum(SKILL_FIST) + registerEnum(SKILL_CLUB) + registerEnum(SKILL_SWORD) + registerEnum(SKILL_AXE) + registerEnum(SKILL_DISTANCE) + registerEnum(SKILL_SHIELD) + registerEnum(SKILL_FISHING) + registerEnum(SKILL_MAGLEVEL) + registerEnum(SKILL_LEVEL) + + registerEnum(SKULL_NONE) + registerEnum(SKULL_YELLOW) + registerEnum(SKULL_GREEN) + registerEnum(SKULL_WHITE) + registerEnum(SKULL_RED) + + registerEnum(FLUID_NONE) + registerEnum(FLUID_WATER) + registerEnum(FLUID_WINE) + registerEnum(FLUID_BEER) + registerEnum(FLUID_MUD) + registerEnum(FLUID_BLOOD) + registerEnum(FLUID_SLIME) + registerEnum(FLUID_OIL) + registerEnum(FLUID_URINE) + registerEnum(FLUID_MILK) + registerEnum(FLUID_MANAFLUID) + registerEnum(FLUID_LIFEFLUID) + registerEnum(FLUID_LEMONADE) + + registerEnum(TALKTYPE_SAY) + registerEnum(TALKTYPE_WHISPER) + registerEnum(TALKTYPE_YELL) + registerEnum(TALKTYPE_CHANNEL_Y) + registerEnum(TALKTYPE_CHANNEL_O) + registerEnum(TALKTYPE_BROADCAST) + registerEnum(TALKTYPE_CHANNEL_R1) + registerEnum(TALKTYPE_MONSTER_SAY) + registerEnum(TALKTYPE_MONSTER_YELL) + registerEnum(TALKTYPE_CHANNEL_R2) + + registerEnum(TEXTCOLOR_BLUE) + registerEnum(TEXTCOLOR_LIGHTGREEN) + registerEnum(TEXTCOLOR_LIGHTBLUE) + registerEnum(TEXTCOLOR_MAYABLUE) + registerEnum(TEXTCOLOR_DARKRED) + registerEnum(TEXTCOLOR_LIGHTGREY) + registerEnum(TEXTCOLOR_SKYBLUE) + registerEnum(TEXTCOLOR_PURPLE) + registerEnum(TEXTCOLOR_RED) + registerEnum(TEXTCOLOR_ORANGE) + registerEnum(TEXTCOLOR_YELLOW) + registerEnum(TEXTCOLOR_WHITE_EXP) + registerEnum(TEXTCOLOR_NONE) + + registerEnum(TILESTATE_NONE) + registerEnum(TILESTATE_PROTECTIONZONE) + registerEnum(TILESTATE_NOPVPZONE) + registerEnum(TILESTATE_NOLOGOUT) + registerEnum(TILESTATE_PVPZONE) + registerEnum(TILESTATE_REFRESH) + registerEnum(TILESTATE_TELEPORT) + registerEnum(TILESTATE_MAGICFIELD) + registerEnum(TILESTATE_MAILBOX) + registerEnum(TILESTATE_BED) + registerEnum(TILESTATE_DEPOT) + registerEnum(TILESTATE_BLOCKSOLID) + registerEnum(TILESTATE_BLOCKPATH) + registerEnum(TILESTATE_IMMOVABLEBLOCKSOLID) + registerEnum(TILESTATE_IMMOVABLEBLOCKPATH) + registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH) + registerEnum(TILESTATE_NOFIELDBLOCKPATH) + registerEnum(TILESTATE_SUPPORTS_HANGABLE) + + registerEnum(WEAPON_NONE) + registerEnum(WEAPON_SWORD) + registerEnum(WEAPON_CLUB) + registerEnum(WEAPON_AXE) + registerEnum(WEAPON_SHIELD) + registerEnum(WEAPON_DISTANCE) + registerEnum(WEAPON_WAND) + registerEnum(WEAPON_AMMO) + + registerEnum(WORLD_TYPE_NO_PVP) + registerEnum(WORLD_TYPE_PVP) + registerEnum(WORLD_TYPE_PVP_ENFORCED) + + // Use with container:addItem, container:addItemEx and possibly other functions. + registerEnum(FLAG_NOLIMIT) + registerEnum(FLAG_IGNOREBLOCKITEM) + registerEnum(FLAG_IGNOREBLOCKCREATURE) + registerEnum(FLAG_CHILDISOWNER) + registerEnum(FLAG_PATHFINDING) + registerEnum(FLAG_IGNOREFIELDDAMAGE) + registerEnum(FLAG_IGNORENOTMOVEABLE) + registerEnum(FLAG_IGNOREAUTOSTACK) + + // Use with itemType:getSlotPosition + registerEnum(SLOTP_WHEREEVER) + registerEnum(SLOTP_HEAD) + registerEnum(SLOTP_NECKLACE) + registerEnum(SLOTP_BACKPACK) + registerEnum(SLOTP_ARMOR) + registerEnum(SLOTP_RIGHT) + registerEnum(SLOTP_LEFT) + registerEnum(SLOTP_LEGS) + registerEnum(SLOTP_FEET) + registerEnum(SLOTP_RING) + registerEnum(SLOTP_AMMO) + registerEnum(SLOTP_DEPOT) + registerEnum(SLOTP_TWO_HAND) + + // Use with house:getAccessList, house:setAccessList + registerEnum(GUEST_LIST) + registerEnum(SUBOWNER_LIST) + + // Use with Game.getReturnMessage + registerEnum(RETURNVALUE_NOERROR) + registerEnum(RETURNVALUE_NOTPOSSIBLE) + registerEnum(RETURNVALUE_NOTENOUGHROOM) + registerEnum(RETURNVALUE_PLAYERISPZLOCKED) + registerEnum(RETURNVALUE_PLAYERISNOTINVITED) + registerEnum(RETURNVALUE_CANNOTTHROW) + registerEnum(RETURNVALUE_THEREISNOWAY) + registerEnum(RETURNVALUE_DESTINATIONOUTOFREACH) + registerEnum(RETURNVALUE_CREATUREBLOCK) + registerEnum(RETURNVALUE_NOTMOVEABLE) + registerEnum(RETURNVALUE_DROPTWOHANDEDITEM) + registerEnum(RETURNVALUE_BOTHHANDSNEEDTOBEFREE) + registerEnum(RETURNVALUE_CANONLYUSEONEWEAPON) + registerEnum(RETURNVALUE_NEEDEXCHANGE) + registerEnum(RETURNVALUE_CANNOTBEDRESSED) + registerEnum(RETURNVALUE_PUTTHISOBJECTINYOURHAND) + registerEnum(RETURNVALUE_PUTTHISOBJECTINBOTHHANDS) + registerEnum(RETURNVALUE_TOOFARAWAY) + registerEnum(RETURNVALUE_FIRSTGODOWNSTAIRS) + registerEnum(RETURNVALUE_FIRSTGOUPSTAIRS) + registerEnum(RETURNVALUE_CONTAINERNOTENOUGHROOM) + registerEnum(RETURNVALUE_NOTENOUGHCAPACITY) + registerEnum(RETURNVALUE_CANNOTPICKUP) + registerEnum(RETURNVALUE_THISISIMPOSSIBLE) + registerEnum(RETURNVALUE_DEPOTISFULL) + registerEnum(RETURNVALUE_CREATUREDOESNOTEXIST) + registerEnum(RETURNVALUE_CANNOTUSETHISOBJECT) + registerEnum(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE) + registerEnum(RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE) + registerEnum(RETURNVALUE_YOUAREALREADYTRADING) + registerEnum(RETURNVALUE_THISPLAYERISALREADYTRADING) + registerEnum(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT) + registerEnum(RETURNVALUE_DIRECTPLAYERSHOOT) + registerEnum(RETURNVALUE_NOTENOUGHLEVEL) + registerEnum(RETURNVALUE_NOTENOUGHMAGICLEVEL) + registerEnum(RETURNVALUE_NOTENOUGHMANA) + registerEnum(RETURNVALUE_NOTENOUGHSOUL) + registerEnum(RETURNVALUE_YOUAREEXHAUSTED) + registerEnum(RETURNVALUE_PLAYERISNOTREACHABLE) + registerEnum(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES) + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE) + registerEnum(RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE) + registerEnum(RETURNVALUE_YOUCANONLYUSEITONCREATURES) + registerEnum(RETURNVALUE_CREATUREISNOTREACHABLE) + registerEnum(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS) + registerEnum(RETURNVALUE_YOUNEEDPREMIUMACCOUNT) + registerEnum(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL) + registerEnum(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL) + registerEnum(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL) + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE) + registerEnum(RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE) + registerEnum(RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE) + registerEnum(RETURNVALUE_YOUCANNOTLOGOUTHERE) + registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL) + registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE) + registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS) + registerEnum(RETURNVALUE_NAMEISTOOAMBIGIOUS) + registerEnum(RETURNVALUE_CANONLYUSEONESHIELD) + registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE) + registerEnum(RETURNVALUE_YOUARENOTTHEOWNER) + + // _G + registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); + registerGlobalBoolean("VIRTUAL_PARENT", true); + + registerGlobalMethod("isType", LuaScriptInterface::luaIsType); + registerGlobalMethod("rawgetmetatable", LuaScriptInterface::luaRawGetMetatable); + + // configKeys + registerTable("configKeys"); + + registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT) + registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT) + registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES) + registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS) + registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM) + registerEnumIn("configKeys", ConfigManager::REPLACE_KICK_ON_LOGIN) + registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES) + registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS) + registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE) + registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS) + registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS) + registerEnumIn("configKeys", ConfigManager::TELEPORT_NEWBIES) + + registerEnumIn("configKeys", ConfigManager::MAP_NAME) + registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD) + registerEnumIn("configKeys", ConfigManager::SERVER_NAME) + registerEnumIn("configKeys", ConfigManager::OWNER_NAME) + registerEnumIn("configKeys", ConfigManager::OWNER_EMAIL) + registerEnumIn("configKeys", ConfigManager::URL) + registerEnumIn("configKeys", ConfigManager::LOCATION) + registerEnumIn("configKeys", ConfigManager::IP) + registerEnumIn("configKeys", ConfigManager::MOTD) + registerEnumIn("configKeys", ConfigManager::WORLD_TYPE) + registerEnumIn("configKeys", ConfigManager::MYSQL_HOST) + registerEnumIn("configKeys", ConfigManager::MYSQL_USER) + registerEnumIn("configKeys", ConfigManager::MYSQL_PASS) + registerEnumIn("configKeys", ConfigManager::MYSQL_DB) + registerEnumIn("configKeys", ConfigManager::MYSQL_SOCK) + registerEnumIn("configKeys", ConfigManager::DEFAULT_PRIORITY) + registerEnumIn("configKeys", ConfigManager::MAP_AUTHOR) + + registerEnumIn("configKeys", ConfigManager::SQL_PORT) + registerEnumIn("configKeys", ConfigManager::MAX_PLAYERS) + registerEnumIn("configKeys", ConfigManager::PZ_LOCKED) + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRANGE) + registerEnumIn("configKeys", ConfigManager::DEFAULT_DESPAWNRADIUS) + registerEnumIn("configKeys", ConfigManager::RATE_EXPERIENCE) + registerEnumIn("configKeys", ConfigManager::RATE_SKILL) + registerEnumIn("configKeys", ConfigManager::RATE_LOOT) + registerEnumIn("configKeys", ConfigManager::RATE_MAGIC) + registerEnumIn("configKeys", ConfigManager::RATE_SPAWN) + registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER) + registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL) + registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL) + registerEnumIn("configKeys", ConfigManager::KICK_AFTER_MINUTES) + registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL) + registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT) + registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT) + registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::RED_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::GAME_PORT) + registerEnumIn("configKeys", ConfigManager::LOGIN_PORT) + registerEnumIn("configKeys", ConfigManager::STATUS_PORT) + registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY) + registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE) + registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND) + registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN) + registerEnumIn("configKeys", ConfigManager::NEWBIE_LEVEL_THRESHOLD) + + // os + registerMethod("os", "mtime", LuaScriptInterface::luaSystemTime); + + // table + registerMethod("table", "create", LuaScriptInterface::luaTableCreate); + + // Game + registerTable("Game"); + + registerMethod("Game", "getSpectators", LuaScriptInterface::luaGameGetSpectators); + registerMethod("Game", "getPlayers", LuaScriptInterface::luaGameGetPlayers); + registerMethod("Game", "loadMap", LuaScriptInterface::luaGameLoadMap); + + registerMethod("Game", "getExperienceStage", LuaScriptInterface::luaGameGetExperienceStage); + registerMethod("Game", "getMonsterCount", LuaScriptInterface::luaGameGetMonsterCount); + registerMethod("Game", "getPlayerCount", LuaScriptInterface::luaGameGetPlayerCount); + registerMethod("Game", "getNpcCount", LuaScriptInterface::luaGameGetNpcCount); + + registerMethod("Game", "getTowns", LuaScriptInterface::luaGameGetTowns); + registerMethod("Game", "getHouses", LuaScriptInterface::luaGameGetHouses); + + registerMethod("Game", "getGameState", LuaScriptInterface::luaGameGetGameState); + registerMethod("Game", "setGameState", LuaScriptInterface::luaGameSetGameState); + + registerMethod("Game", "getWorldType", LuaScriptInterface::luaGameGetWorldType); + registerMethod("Game", "setWorldType", LuaScriptInterface::luaGameSetWorldType); + + registerMethod("Game", "getReturnMessage", LuaScriptInterface::luaGameGetReturnMessage); + + registerMethod("Game", "createItem", LuaScriptInterface::luaGameCreateItem); + registerMethod("Game", "createContainer", LuaScriptInterface::luaGameCreateContainer); + registerMethod("Game", "createMonster", LuaScriptInterface::luaGameCreateMonster); + registerMethod("Game", "createNpc", LuaScriptInterface::luaGameCreateNpc); + registerMethod("Game", "createTile", LuaScriptInterface::luaGameCreateTile); + + registerMethod("Game", "startRaid", LuaScriptInterface::luaGameStartRaid); + + // Variant + registerClass("Variant", "", LuaScriptInterface::luaVariantCreate); + + registerMethod("Variant", "getNumber", LuaScriptInterface::luaVariantGetNumber); + registerMethod("Variant", "getString", LuaScriptInterface::luaVariantGetString); + registerMethod("Variant", "getPosition", LuaScriptInterface::luaVariantGetPosition); + + // Position + registerClass("Position", "", LuaScriptInterface::luaPositionCreate); + registerMetaMethod("Position", "__add", LuaScriptInterface::luaPositionAdd); + registerMetaMethod("Position", "__sub", LuaScriptInterface::luaPositionSub); + registerMetaMethod("Position", "__eq", LuaScriptInterface::luaPositionCompare); + + registerMethod("Position", "getDistance", LuaScriptInterface::luaPositionGetDistance); + registerMethod("Position", "isSightClear", LuaScriptInterface::luaPositionIsSightClear); + + registerMethod("Position", "sendMagicEffect", LuaScriptInterface::luaPositionSendMagicEffect); + registerMethod("Position", "sendDistanceEffect", LuaScriptInterface::luaPositionSendDistanceEffect); + registerMethod("Position", "sendMonsterSay", LuaScriptInterface::luaPositionSendMonsterSay); + + // Tile + registerClass("Tile", "", LuaScriptInterface::luaTileCreate); + registerMetaMethod("Tile", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Tile", "getPosition", LuaScriptInterface::luaTileGetPosition); + registerMethod("Tile", "getGround", LuaScriptInterface::luaTileGetGround); + registerMethod("Tile", "getThing", LuaScriptInterface::luaTileGetThing); + registerMethod("Tile", "getThingCount", LuaScriptInterface::luaTileGetThingCount); + registerMethod("Tile", "getTopVisibleThing", LuaScriptInterface::luaTileGetTopVisibleThing); + + registerMethod("Tile", "getTopTopItem", LuaScriptInterface::luaTileGetTopTopItem); + registerMethod("Tile", "getTopDownItem", LuaScriptInterface::luaTileGetTopDownItem); + registerMethod("Tile", "getFieldItem", LuaScriptInterface::luaTileGetFieldItem); + + registerMethod("Tile", "getItemById", LuaScriptInterface::luaTileGetItemById); + registerMethod("Tile", "getItemByType", LuaScriptInterface::luaTileGetItemByType); + registerMethod("Tile", "getItemByTopOrder", LuaScriptInterface::luaTileGetItemByTopOrder); + registerMethod("Tile", "getItemCountById", LuaScriptInterface::luaTileGetItemCountById); + + registerMethod("Tile", "getBottomCreature", LuaScriptInterface::luaTileGetBottomCreature); + registerMethod("Tile", "getTopCreature", LuaScriptInterface::luaTileGetTopCreature); + registerMethod("Tile", "getBottomVisibleCreature", LuaScriptInterface::luaTileGetBottomVisibleCreature); + registerMethod("Tile", "getTopVisibleCreature", LuaScriptInterface::luaTileGetTopVisibleCreature); + + registerMethod("Tile", "getItems", LuaScriptInterface::luaTileGetItems); + registerMethod("Tile", "getItemCount", LuaScriptInterface::luaTileGetItemCount); + registerMethod("Tile", "getDownItemCount", LuaScriptInterface::luaTileGetDownItemCount); + registerMethod("Tile", "getTopItemCount", LuaScriptInterface::luaTileGetTopItemCount); + + registerMethod("Tile", "getCreatures", LuaScriptInterface::luaTileGetCreatures); + registerMethod("Tile", "getCreatureCount", LuaScriptInterface::luaTileGetCreatureCount); + + registerMethod("Tile", "getThingIndex", LuaScriptInterface::luaTileGetThingIndex); + + registerMethod("Tile", "hasProperty", LuaScriptInterface::luaTileHasProperty); + registerMethod("Tile", "hasFlag", LuaScriptInterface::luaTileHasFlag); + + registerMethod("Tile", "queryAdd", LuaScriptInterface::luaTileQueryAdd); + + registerMethod("Tile", "getHouse", LuaScriptInterface::luaTileGetHouse); + + // NetworkMessage + registerClass("NetworkMessage", "", LuaScriptInterface::luaNetworkMessageCreate); + registerMetaMethod("NetworkMessage", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("NetworkMessage", "__gc", LuaScriptInterface::luaNetworkMessageDelete); + registerMethod("NetworkMessage", "delete", LuaScriptInterface::luaNetworkMessageDelete); + + registerMethod("NetworkMessage", "getByte", LuaScriptInterface::luaNetworkMessageGetByte); + registerMethod("NetworkMessage", "getU16", LuaScriptInterface::luaNetworkMessageGetU16); + registerMethod("NetworkMessage", "getU32", LuaScriptInterface::luaNetworkMessageGetU32); + registerMethod("NetworkMessage", "getU64", LuaScriptInterface::luaNetworkMessageGetU64); + registerMethod("NetworkMessage", "getString", LuaScriptInterface::luaNetworkMessageGetString); + registerMethod("NetworkMessage", "getPosition", LuaScriptInterface::luaNetworkMessageGetPosition); + + registerMethod("NetworkMessage", "addByte", LuaScriptInterface::luaNetworkMessageAddByte); + registerMethod("NetworkMessage", "addU16", LuaScriptInterface::luaNetworkMessageAddU16); + registerMethod("NetworkMessage", "addU32", LuaScriptInterface::luaNetworkMessageAddU32); + registerMethod("NetworkMessage", "addU64", LuaScriptInterface::luaNetworkMessageAddU64); + registerMethod("NetworkMessage", "addString", LuaScriptInterface::luaNetworkMessageAddString); + registerMethod("NetworkMessage", "addPosition", LuaScriptInterface::luaNetworkMessageAddPosition); + registerMethod("NetworkMessage", "addDouble", LuaScriptInterface::luaNetworkMessageAddDouble); + registerMethod("NetworkMessage", "addItem", LuaScriptInterface::luaNetworkMessageAddItem); + registerMethod("NetworkMessage", "addItemId", LuaScriptInterface::luaNetworkMessageAddItemId); + + registerMethod("NetworkMessage", "reset", LuaScriptInterface::luaNetworkMessageReset); + registerMethod("NetworkMessage", "skipBytes", LuaScriptInterface::luaNetworkMessageSkipBytes); + registerMethod("NetworkMessage", "sendToPlayer", LuaScriptInterface::luaNetworkMessageSendToPlayer); + + // Item + registerClass("Item", "", LuaScriptInterface::luaItemCreate); + registerMetaMethod("Item", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Item", "isItem", LuaScriptInterface::luaItemIsItem); + + registerMethod("Item", "getParent", LuaScriptInterface::luaItemGetParent); + registerMethod("Item", "getTopParent", LuaScriptInterface::luaItemGetTopParent); + + registerMethod("Item", "getId", LuaScriptInterface::luaItemGetId); + + registerMethod("Item", "clone", LuaScriptInterface::luaItemClone); + registerMethod("Item", "split", LuaScriptInterface::luaItemSplit); + registerMethod("Item", "remove", LuaScriptInterface::luaItemRemove); + + registerMethod("Item", "getMovementId", LuaScriptInterface::luaItemGetMovementId); + registerMethod("Item", "setMovementId", LuaScriptInterface::luaItemSetMovementId); + registerMethod("Item", "getActionId", LuaScriptInterface::luaItemGetActionId); + registerMethod("Item", "setActionId", LuaScriptInterface::luaItemSetActionId); + registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); + + registerMethod("Item", "getCount", LuaScriptInterface::luaItemGetCount); + registerMethod("Item", "getCharges", LuaScriptInterface::luaItemGetCharges); + registerMethod("Item", "getFluidType", LuaScriptInterface::luaItemGetFluidType); + registerMethod("Item", "getWeight", LuaScriptInterface::luaItemGetWeight); + + registerMethod("Item", "getSubType", LuaScriptInterface::luaItemGetSubType); + + registerMethod("Item", "getName", LuaScriptInterface::luaItemGetName); + registerMethod("Item", "getPluralName", LuaScriptInterface::luaItemGetPluralName); + registerMethod("Item", "getArticle", LuaScriptInterface::luaItemGetArticle); + + registerMethod("Item", "getPosition", LuaScriptInterface::luaItemGetPosition); + registerMethod("Item", "getTile", LuaScriptInterface::luaItemGetTile); + + registerMethod("Item", "hasAttribute", LuaScriptInterface::luaItemHasAttribute); + registerMethod("Item", "getAttribute", LuaScriptInterface::luaItemGetAttribute); + registerMethod("Item", "setAttribute", LuaScriptInterface::luaItemSetAttribute); + registerMethod("Item", "removeAttribute", LuaScriptInterface::luaItemRemoveAttribute); + + registerMethod("Item", "moveTo", LuaScriptInterface::luaItemMoveTo); + registerMethod("Item", "transform", LuaScriptInterface::luaItemTransform); + registerMethod("Item", "decay", LuaScriptInterface::luaItemDecay); + + registerMethod("Item", "getDescription", LuaScriptInterface::luaItemGetDescription); + + registerMethod("Item", "hasProperty", LuaScriptInterface::luaItemHasProperty); + + // Container + registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); + registerMetaMethod("Container", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Container", "getSize", LuaScriptInterface::luaContainerGetSize); + registerMethod("Container", "getCapacity", LuaScriptInterface::luaContainerGetCapacity); + registerMethod("Container", "getEmptySlots", LuaScriptInterface::luaContainerGetEmptySlots); + + registerMethod("Container", "getItemHoldingCount", LuaScriptInterface::luaContainerGetItemHoldingCount); + registerMethod("Container", "getItemCountById", LuaScriptInterface::luaContainerGetItemCountById); + + registerMethod("Container", "getItem", LuaScriptInterface::luaContainerGetItem); + registerMethod("Container", "hasItem", LuaScriptInterface::luaContainerHasItem); + registerMethod("Container", "addItem", LuaScriptInterface::luaContainerAddItem); + registerMethod("Container", "addItemEx", LuaScriptInterface::luaContainerAddItemEx); + + // Teleport + registerClass("Teleport", "Item", LuaScriptInterface::luaTeleportCreate); + registerMetaMethod("Teleport", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Teleport", "getDestination", LuaScriptInterface::luaTeleportGetDestination); + registerMethod("Teleport", "setDestination", LuaScriptInterface::luaTeleportSetDestination); + + // Creature + registerClass("Creature", "", LuaScriptInterface::luaCreatureCreate); + registerMetaMethod("Creature", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Creature", "getEvents", LuaScriptInterface::luaCreatureGetEvents); + registerMethod("Creature", "registerEvent", LuaScriptInterface::luaCreatureRegisterEvent); + registerMethod("Creature", "unregisterEvent", LuaScriptInterface::luaCreatureUnregisterEvent); + + registerMethod("Creature", "isRemoved", LuaScriptInterface::luaCreatureIsRemoved); + registerMethod("Creature", "isCreature", LuaScriptInterface::luaCreatureIsCreature); + registerMethod("Creature", "isInGhostMode", LuaScriptInterface::luaCreatureIsInGhostMode); + + registerMethod("Creature", "canSee", LuaScriptInterface::luaCreatureCanSee); + registerMethod("Creature", "canSeeCreature", LuaScriptInterface::luaCreatureCanSeeCreature); + + registerMethod("Creature", "getParent", LuaScriptInterface::luaCreatureGetParent); + + registerMethod("Creature", "getId", LuaScriptInterface::luaCreatureGetId); + registerMethod("Creature", "getName", LuaScriptInterface::luaCreatureGetName); + + registerMethod("Creature", "getTarget", LuaScriptInterface::luaCreatureGetTarget); + registerMethod("Creature", "setTarget", LuaScriptInterface::luaCreatureSetTarget); + + registerMethod("Creature", "getFollowCreature", LuaScriptInterface::luaCreatureGetFollowCreature); + registerMethod("Creature", "setFollowCreature", LuaScriptInterface::luaCreatureSetFollowCreature); + + registerMethod("Creature", "getMaster", LuaScriptInterface::luaCreatureGetMaster); + registerMethod("Creature", "setMaster", LuaScriptInterface::luaCreatureSetMaster); + + registerMethod("Creature", "getLight", LuaScriptInterface::luaCreatureGetLight); + registerMethod("Creature", "setLight", LuaScriptInterface::luaCreatureSetLight); + + registerMethod("Creature", "getSpeed", LuaScriptInterface::luaCreatureGetSpeed); + registerMethod("Creature", "getBaseSpeed", LuaScriptInterface::luaCreatureGetBaseSpeed); + registerMethod("Creature", "changeSpeed", LuaScriptInterface::luaCreatureChangeSpeed); + + registerMethod("Creature", "setDropLoot", LuaScriptInterface::luaCreatureSetDropLoot); + + registerMethod("Creature", "getPosition", LuaScriptInterface::luaCreatureGetPosition); + registerMethod("Creature", "getTile", LuaScriptInterface::luaCreatureGetTile); + registerMethod("Creature", "getDirection", LuaScriptInterface::luaCreatureGetDirection); + registerMethod("Creature", "setDirection", LuaScriptInterface::luaCreatureSetDirection); + + registerMethod("Creature", "getHealth", LuaScriptInterface::luaCreatureGetHealth); + registerMethod("Creature", "addHealth", LuaScriptInterface::luaCreatureAddHealth); + registerMethod("Creature", "getMaxHealth", LuaScriptInterface::luaCreatureGetMaxHealth); + registerMethod("Creature", "setMaxHealth", LuaScriptInterface::luaCreatureSetMaxHealth); + + registerMethod("Creature", "getMana", LuaScriptInterface::luaCreatureGetMana); + registerMethod("Creature", "addMana", LuaScriptInterface::luaCreatureAddMana); + registerMethod("Creature", "getMaxMana", LuaScriptInterface::luaCreatureGetMaxMana); + + registerMethod("Creature", "getSkull", LuaScriptInterface::luaCreatureGetSkull); + registerMethod("Creature", "setSkull", LuaScriptInterface::luaCreatureSetSkull); + + registerMethod("Creature", "getOutfit", LuaScriptInterface::luaCreatureGetOutfit); + registerMethod("Creature", "setOutfit", LuaScriptInterface::luaCreatureSetOutfit); + + registerMethod("Creature", "getCondition", LuaScriptInterface::luaCreatureGetCondition); + registerMethod("Creature", "addCondition", LuaScriptInterface::luaCreatureAddCondition); + registerMethod("Creature", "removeCondition", LuaScriptInterface::luaCreatureRemoveCondition); + + registerMethod("Creature", "remove", LuaScriptInterface::luaCreatureRemove); + registerMethod("Creature", "teleportTo", LuaScriptInterface::luaCreatureTeleportTo); + registerMethod("Creature", "say", LuaScriptInterface::luaCreatureSay); + + registerMethod("Creature", "getDamageMap", LuaScriptInterface::luaCreatureGetDamageMap); + + registerMethod("Creature", "getSummons", LuaScriptInterface::luaCreatureGetSummons); + + registerMethod("Creature", "getDescription", LuaScriptInterface::luaCreatureGetDescription); + + registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo); + + // Player + registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate); + registerMetaMethod("Player", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Player", "isPlayer", LuaScriptInterface::luaPlayerIsPlayer); + + registerMethod("Player", "getGuid", LuaScriptInterface::luaPlayerGetGuid); + registerMethod("Player", "getIp", LuaScriptInterface::luaPlayerGetIp); + registerMethod("Player", "getAccountId", LuaScriptInterface::luaPlayerGetAccountId); + registerMethod("Player", "getLastLoginSaved", LuaScriptInterface::luaPlayerGetLastLoginSaved); + registerMethod("Player", "getLastLogout", LuaScriptInterface::luaPlayerGetLastLogout); + registerMethod("Player", "hasFlag", LuaScriptInterface::luaPlayerHasFlag); + + registerMethod("Player", "getAccountType", LuaScriptInterface::luaPlayerGetAccountType); + registerMethod("Player", "setAccountType", LuaScriptInterface::luaPlayerSetAccountType); + + registerMethod("Player", "getCapacity", LuaScriptInterface::luaPlayerGetCapacity); + registerMethod("Player", "setCapacity", LuaScriptInterface::luaPlayerSetCapacity); + + registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity); + + registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest); + + registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps); + registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd); + registerMethod("Player", "setPlayerKillerEnd", LuaScriptInterface::luaPlayerSetPlayerKillerEnd); + registerMethod("Player", "getDeathPenalty", LuaScriptInterface::luaPlayerGetDeathPenalty); + + registerMethod("Player", "getExperience", LuaScriptInterface::luaPlayerGetExperience); + registerMethod("Player", "addExperience", LuaScriptInterface::luaPlayerAddExperience); + registerMethod("Player", "removeExperience", LuaScriptInterface::luaPlayerRemoveExperience); + registerMethod("Player", "getLevel", LuaScriptInterface::luaPlayerGetLevel); + + registerMethod("Player", "getMagicLevel", LuaScriptInterface::luaPlayerGetMagicLevel); + registerMethod("Player", "getBaseMagicLevel", LuaScriptInterface::luaPlayerGetBaseMagicLevel); + registerMethod("Player", "setMaxMana", LuaScriptInterface::luaPlayerSetMaxMana); + registerMethod("Player", "getManaSpent", LuaScriptInterface::luaPlayerGetManaSpent); + registerMethod("Player", "addManaSpent", LuaScriptInterface::luaPlayerAddManaSpent); + + registerMethod("Player", "getBaseMaxHealth", LuaScriptInterface::luaPlayerGetBaseMaxHealth); + registerMethod("Player", "getBaseMaxMana", LuaScriptInterface::luaPlayerGetBaseMaxMana); + + registerMethod("Player", "getSkillLevel", LuaScriptInterface::luaPlayerGetSkillLevel); + registerMethod("Player", "getEffectiveSkillLevel", LuaScriptInterface::luaPlayerGetEffectiveSkillLevel); + registerMethod("Player", "getSkillPercent", LuaScriptInterface::luaPlayerGetSkillPercent); + registerMethod("Player", "getSkillTries", LuaScriptInterface::luaPlayerGetSkillTries); + registerMethod("Player", "addSkillTries", LuaScriptInterface::luaPlayerAddSkillTries); + + registerMethod("Player", "getItemCount", LuaScriptInterface::luaPlayerGetItemCount); + registerMethod("Player", "getItemById", LuaScriptInterface::luaPlayerGetItemById); + + registerMethod("Player", "getVocation", LuaScriptInterface::luaPlayerGetVocation); + registerMethod("Player", "setVocation", LuaScriptInterface::luaPlayerSetVocation); + + registerMethod("Player", "getSex", LuaScriptInterface::luaPlayerGetSex); + registerMethod("Player", "setSex", LuaScriptInterface::luaPlayerSetSex); + + registerMethod("Player", "getTown", LuaScriptInterface::luaPlayerGetTown); + registerMethod("Player", "setTown", LuaScriptInterface::luaPlayerSetTown); + + registerMethod("Player", "getGuild", LuaScriptInterface::luaPlayerGetGuild); + registerMethod("Player", "setGuild", LuaScriptInterface::luaPlayerSetGuild); + + registerMethod("Player", "getGuildLevel", LuaScriptInterface::luaPlayerGetGuildLevel); + registerMethod("Player", "setGuildLevel", LuaScriptInterface::luaPlayerSetGuildLevel); + + registerMethod("Player", "getGuildNick", LuaScriptInterface::luaPlayerGetGuildNick); + registerMethod("Player", "setGuildNick", LuaScriptInterface::luaPlayerSetGuildNick); + + registerMethod("Player", "getGroup", LuaScriptInterface::luaPlayerGetGroup); + registerMethod("Player", "setGroup", LuaScriptInterface::luaPlayerSetGroup); + + registerMethod("Player", "getSoul", LuaScriptInterface::luaPlayerGetSoul); + registerMethod("Player", "addSoul", LuaScriptInterface::luaPlayerAddSoul); + registerMethod("Player", "getMaxSoul", LuaScriptInterface::luaPlayerGetMaxSoul); + + registerMethod("Player", "getBankBalance", LuaScriptInterface::luaPlayerGetBankBalance); + registerMethod("Player", "setBankBalance", LuaScriptInterface::luaPlayerSetBankBalance); + + registerMethod("Player", "getStorageValue", LuaScriptInterface::luaPlayerGetStorageValue); + registerMethod("Player", "setStorageValue", LuaScriptInterface::luaPlayerSetStorageValue); + + registerMethod("Player", "addItem", LuaScriptInterface::luaPlayerAddItem); + registerMethod("Player", "addItemEx", LuaScriptInterface::luaPlayerAddItemEx); + registerMethod("Player", "removeItem", LuaScriptInterface::luaPlayerRemoveItem); + + registerMethod("Player", "getMoney", LuaScriptInterface::luaPlayerGetMoney); + registerMethod("Player", "addMoney", LuaScriptInterface::luaPlayerAddMoney); + registerMethod("Player", "removeMoney", LuaScriptInterface::luaPlayerRemoveMoney); + + registerMethod("Player", "showTextDialog", LuaScriptInterface::luaPlayerShowTextDialog); + + registerMethod("Player", "sendTextMessage", LuaScriptInterface::luaPlayerSendTextMessage); + registerMethod("Player", "sendPrivateMessage", LuaScriptInterface::luaPlayerSendPrivateMessage); + registerMethod("Player", "channelSay", LuaScriptInterface::luaPlayerChannelSay); + registerMethod("Player", "openChannel", LuaScriptInterface::luaPlayerOpenChannel); + + registerMethod("Player", "getSlotItem", LuaScriptInterface::luaPlayerGetSlotItem); + + registerMethod("Player", "getParty", LuaScriptInterface::luaPlayerGetParty); + + registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow); + + registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays); + registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays); + registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays); + + registerMethod("Player", "hasBlessing", LuaScriptInterface::luaPlayerHasBlessing); + registerMethod("Player", "addBlessing", LuaScriptInterface::luaPlayerAddBlessing); + registerMethod("Player", "removeBlessing", LuaScriptInterface::luaPlayerRemoveBlessing); + + registerMethod("Player", "canLearnSpell", LuaScriptInterface::luaPlayerCanLearnSpell); + registerMethod("Player", "learnSpell", LuaScriptInterface::luaPlayerLearnSpell); + registerMethod("Player", "forgetSpell", LuaScriptInterface::luaPlayerForgetSpell); + registerMethod("Player", "hasLearnedSpell", LuaScriptInterface::luaPlayerHasLearnedSpell); + + registerMethod("Player", "save", LuaScriptInterface::luaPlayerSave); + + registerMethod("Player", "isPzLocked", LuaScriptInterface::luaPlayerIsPzLocked); + + registerMethod("Player", "getClient", LuaScriptInterface::luaPlayerGetClient); + registerMethod("Player", "getHouse", LuaScriptInterface::luaPlayerGetHouse); + + registerMethod("Player", "setGhostMode", LuaScriptInterface::luaPlayerSetGhostMode); + + registerMethod("Player", "getContainerId", LuaScriptInterface::luaPlayerGetContainerId); + registerMethod("Player", "getContainerById", LuaScriptInterface::luaPlayerGetContainerById); + registerMethod("Player", "getContainerIndex", LuaScriptInterface::luaPlayerGetContainerIndex); + + registerMethod("Player", "getTotalDamage", LuaScriptInterface::luaPlayerGetTotalDamage); + + // Monster + registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); + registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Monster", "isMonster", LuaScriptInterface::luaMonsterIsMonster); + + registerMethod("Monster", "getType", LuaScriptInterface::luaMonsterGetType); + + registerMethod("Monster", "getSpawnPosition", LuaScriptInterface::luaMonsterGetSpawnPosition); + registerMethod("Monster", "isInSpawnRange", LuaScriptInterface::luaMonsterIsInSpawnRange); + + registerMethod("Monster", "isIdle", LuaScriptInterface::luaMonsterIsIdle); + registerMethod("Monster", "setIdle", LuaScriptInterface::luaMonsterSetIdle); + + registerMethod("Monster", "isTarget", LuaScriptInterface::luaMonsterIsTarget); + registerMethod("Monster", "isOpponent", LuaScriptInterface::luaMonsterIsOpponent); + registerMethod("Monster", "isFriend", LuaScriptInterface::luaMonsterIsFriend); + + registerMethod("Monster", "addFriend", LuaScriptInterface::luaMonsterAddFriend); + registerMethod("Monster", "removeFriend", LuaScriptInterface::luaMonsterRemoveFriend); + registerMethod("Monster", "getFriendList", LuaScriptInterface::luaMonsterGetFriendList); + registerMethod("Monster", "getFriendCount", LuaScriptInterface::luaMonsterGetFriendCount); + + registerMethod("Monster", "addTarget", LuaScriptInterface::luaMonsterAddTarget); + registerMethod("Monster", "removeTarget", LuaScriptInterface::luaMonsterRemoveTarget); + registerMethod("Monster", "getTargetList", LuaScriptInterface::luaMonsterGetTargetList); + registerMethod("Monster", "getTargetCount", LuaScriptInterface::luaMonsterGetTargetCount); + + registerMethod("Monster", "selectTarget", LuaScriptInterface::luaMonsterSelectTarget); + registerMethod("Monster", "searchTarget", LuaScriptInterface::luaMonsterSearchTarget); + + // Npc + registerClass("Npc", "Creature", LuaScriptInterface::luaNpcCreate); + registerMetaMethod("Npc", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Npc", "isNpc", LuaScriptInterface::luaNpcIsNpc); + + registerMethod("Npc", "setMasterPos", LuaScriptInterface::luaNpcSetMasterPos); + + // Guild + registerClass("Guild", "", LuaScriptInterface::luaGuildCreate); + registerMetaMethod("Guild", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Guild", "getId", LuaScriptInterface::luaGuildGetId); + registerMethod("Guild", "getName", LuaScriptInterface::luaGuildGetName); + registerMethod("Guild", "getMembersOnline", LuaScriptInterface::luaGuildGetMembersOnline); + + registerMethod("Guild", "addRank", LuaScriptInterface::luaGuildAddRank); + registerMethod("Guild", "getRankById", LuaScriptInterface::luaGuildGetRankById); + registerMethod("Guild", "getRankByLevel", LuaScriptInterface::luaGuildGetRankByLevel); + + // Group + registerClass("Group", "", LuaScriptInterface::luaGroupCreate); + registerMetaMethod("Group", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Group", "getId", LuaScriptInterface::luaGroupGetId); + registerMethod("Group", "getName", LuaScriptInterface::luaGroupGetName); + registerMethod("Group", "getFlags", LuaScriptInterface::luaGroupGetFlags); + registerMethod("Group", "getAccess", LuaScriptInterface::luaGroupGetAccess); + registerMethod("Group", "getMaxDepotItems", LuaScriptInterface::luaGroupGetMaxDepotItems); + registerMethod("Group", "getMaxVipEntries", LuaScriptInterface::luaGroupGetMaxVipEntries); + + // Vocation + registerClass("Vocation", "", LuaScriptInterface::luaVocationCreate); + registerMetaMethod("Vocation", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Vocation", "getId", LuaScriptInterface::luaVocationGetId); + registerMethod("Vocation", "getName", LuaScriptInterface::luaVocationGetName); + registerMethod("Vocation", "getDescription", LuaScriptInterface::luaVocationGetDescription); + + registerMethod("Vocation", "getRequiredSkillTries", LuaScriptInterface::luaVocationGetRequiredSkillTries); + registerMethod("Vocation", "getRequiredManaSpent", LuaScriptInterface::luaVocationGetRequiredManaSpent); + + registerMethod("Vocation", "getCapacityGain", LuaScriptInterface::luaVocationGetCapacityGain); + + registerMethod("Vocation", "getHealthGain", LuaScriptInterface::luaVocationGetHealthGain); + registerMethod("Vocation", "getHealthGainTicks", LuaScriptInterface::luaVocationGetHealthGainTicks); + registerMethod("Vocation", "getHealthGainAmount", LuaScriptInterface::luaVocationGetHealthGainAmount); + + registerMethod("Vocation", "getManaGain", LuaScriptInterface::luaVocationGetManaGain); + registerMethod("Vocation", "getManaGainTicks", LuaScriptInterface::luaVocationGetManaGainTicks); + registerMethod("Vocation", "getManaGainAmount", LuaScriptInterface::luaVocationGetManaGainAmount); + + registerMethod("Vocation", "getMaxSoul", LuaScriptInterface::luaVocationGetMaxSoul); + registerMethod("Vocation", "getSoulGainTicks", LuaScriptInterface::luaVocationGetSoulGainTicks); + + registerMethod("Vocation", "getAttackSpeed", LuaScriptInterface::luaVocationGetAttackSpeed); + registerMethod("Vocation", "getBaseSpeed", LuaScriptInterface::luaVocationGetBaseSpeed); + + registerMethod("Vocation", "getDemotion", LuaScriptInterface::luaVocationGetDemotion); + registerMethod("Vocation", "getPromotion", LuaScriptInterface::luaVocationGetPromotion); + + // Town + registerClass("Town", "", LuaScriptInterface::luaTownCreate); + registerMetaMethod("Town", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Town", "getId", LuaScriptInterface::luaTownGetId); + registerMethod("Town", "getName", LuaScriptInterface::luaTownGetName); + registerMethod("Town", "getTemplePosition", LuaScriptInterface::luaTownGetTemplePosition); + + // House + registerClass("House", "", LuaScriptInterface::luaHouseCreate); + registerMetaMethod("House", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("House", "getId", LuaScriptInterface::luaHouseGetId); + registerMethod("House", "getName", LuaScriptInterface::luaHouseGetName); + registerMethod("House", "getTown", LuaScriptInterface::luaHouseGetTown); + registerMethod("House", "getExitPosition", LuaScriptInterface::luaHouseGetExitPosition); + registerMethod("House", "getRent", LuaScriptInterface::luaHouseGetRent); + + registerMethod("House", "getOwnerGuid", LuaScriptInterface::luaHouseGetOwnerGuid); + registerMethod("House", "setOwnerGuid", LuaScriptInterface::luaHouseSetOwnerGuid); + + registerMethod("House", "getBeds", LuaScriptInterface::luaHouseGetBeds); + registerMethod("House", "getBedCount", LuaScriptInterface::luaHouseGetBedCount); + + registerMethod("House", "getDoors", LuaScriptInterface::luaHouseGetDoors); + registerMethod("House", "getDoorCount", LuaScriptInterface::luaHouseGetDoorCount); + + registerMethod("House", "getTiles", LuaScriptInterface::luaHouseGetTiles); + registerMethod("House", "getTileCount", LuaScriptInterface::luaHouseGetTileCount); + + registerMethod("House", "getAccessList", LuaScriptInterface::luaHouseGetAccessList); + registerMethod("House", "setAccessList", LuaScriptInterface::luaHouseSetAccessList); + + // ItemType + registerClass("ItemType", "", LuaScriptInterface::luaItemTypeCreate); + registerMetaMethod("ItemType", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("ItemType", "isCorpse", LuaScriptInterface::luaItemTypeIsCorpse); + registerMethod("ItemType", "isDoor", LuaScriptInterface::luaItemTypeIsDoor); + registerMethod("ItemType", "isContainer", LuaScriptInterface::luaItemTypeIsContainer); + registerMethod("ItemType", "isChest", LuaScriptInterface::luaItemTypeIsChest); + registerMethod("ItemType", "isFluidContainer", LuaScriptInterface::luaItemTypeIsFluidContainer); + registerMethod("ItemType", "isMovable", LuaScriptInterface::luaItemTypeIsMovable); + registerMethod("ItemType", "isRune", LuaScriptInterface::luaItemTypeIsRune); + registerMethod("ItemType", "isStackable", LuaScriptInterface::luaItemTypeIsStackable); + registerMethod("ItemType", "isReadable", LuaScriptInterface::luaItemTypeIsReadable); + registerMethod("ItemType", "isWritable", LuaScriptInterface::luaItemTypeIsWritable); + registerMethod("ItemType", "isMagicField", LuaScriptInterface::luaItemTypeIsMagicField); + registerMethod("ItemType", "isSplash", LuaScriptInterface::luaItemTypeIsSplash); + registerMethod("ItemType", "isKey", LuaScriptInterface::luaItemTypeIsKey); + registerMethod("ItemType", "isDisguised", LuaScriptInterface::luaItemTypeIsDisguised); + registerMethod("ItemType", "isDestroyable", LuaScriptInterface::luaItemTypeIsDestroyable); + registerMethod("ItemType", "isGroundTile", LuaScriptInterface::luaItemTypeIsGroundTile); + + registerMethod("ItemType", "getType", LuaScriptInterface::luaItemTypeGetType); + registerMethod("ItemType", "getId", LuaScriptInterface::luaItemTypeGetId); + registerMethod("ItemType", "getDisguiseId", LuaScriptInterface::luaItemTypeGetDisguiseId); + registerMethod("ItemType", "getName", LuaScriptInterface::luaItemTypeGetName); + registerMethod("ItemType", "getPluralName", LuaScriptInterface::luaItemTypeGetPluralName); + registerMethod("ItemType", "getArticle", LuaScriptInterface::luaItemTypeGetArticle); + registerMethod("ItemType", "getDescription", LuaScriptInterface::luaItemTypeGetDescription); + registerMethod("ItemType", "getSlotPosition", LuaScriptInterface::luaItemTypeGetSlotPosition); + registerMethod("ItemType", "getDestroyTarget", LuaScriptInterface::luaItemTypeGetDestroyTarget); + + registerMethod("ItemType", "getCharges", LuaScriptInterface::luaItemTypeGetCharges); + registerMethod("ItemType", "getFluidSource", LuaScriptInterface::luaItemTypeGetFluidSource); + registerMethod("ItemType", "getCapacity", LuaScriptInterface::luaItemTypeGetCapacity); + registerMethod("ItemType", "getWeight", LuaScriptInterface::luaItemTypeGetWeight); + + registerMethod("ItemType", "getShootRange", LuaScriptInterface::luaItemTypeGetShootRange); + + registerMethod("ItemType", "getAttack", LuaScriptInterface::luaItemTypeGetAttack); + registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense); + registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); + registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType); + + registerMethod("ItemType", "getTransformEquipId", LuaScriptInterface::luaItemTypeGetTransformEquipId); + registerMethod("ItemType", "getTransformDeEquipId", LuaScriptInterface::luaItemTypeGetTransformDeEquipId); + registerMethod("ItemType", "getDecayId", LuaScriptInterface::luaItemTypeGetDecayId); + registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition); + registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel); + + registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType); + + // Combat + registerClass("Combat", "", LuaScriptInterface::luaCombatCreate); + registerMetaMethod("Combat", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Combat", "setParameter", LuaScriptInterface::luaCombatSetParameter); + registerMethod("Combat", "setFormula", LuaScriptInterface::luaCombatSetFormula); + + registerMethod("Combat", "setArea", LuaScriptInterface::luaCombatSetArea); + registerMethod("Combat", "setCondition", LuaScriptInterface::luaCombatSetCondition); + registerMethod("Combat", "setCallback", LuaScriptInterface::luaCombatSetCallback); + + registerMethod("Combat", "execute", LuaScriptInterface::luaCombatExecute); + + // Condition + registerClass("Condition", "", LuaScriptInterface::luaConditionCreate); + registerMetaMethod("Condition", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("Condition", "__gc", LuaScriptInterface::luaConditionDelete); + registerMethod("Condition", "delete", LuaScriptInterface::luaConditionDelete); + + registerMethod("Condition", "getId", LuaScriptInterface::luaConditionGetId); + registerMethod("Condition", "getSubId", LuaScriptInterface::luaConditionGetSubId); + registerMethod("Condition", "getType", LuaScriptInterface::luaConditionGetType); + registerMethod("Condition", "getIcons", LuaScriptInterface::luaConditionGetIcons); + registerMethod("Condition", "getEndTime", LuaScriptInterface::luaConditionGetEndTime); + + registerMethod("Condition", "clone", LuaScriptInterface::luaConditionClone); + + registerMethod("Condition", "getTicks", LuaScriptInterface::luaConditionGetTicks); + registerMethod("Condition", "setTicks", LuaScriptInterface::luaConditionSetTicks); + + registerMethod("Condition", "setParameter", LuaScriptInterface::luaConditionSetParameter); + registerMethod("Condition", "setSpeedDelta", LuaScriptInterface::luaConditionSetSpeedDelta); + registerMethod("Condition", "setOutfit", LuaScriptInterface::luaConditionSetOutfit); + + registerMethod("Condition", "setTiming", LuaScriptInterface::luaConditionSetTiming); + + // MonsterType + registerClass("MonsterType", "", LuaScriptInterface::luaMonsterTypeCreate); + registerMetaMethod("MonsterType", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("MonsterType", "isAttackable", LuaScriptInterface::luaMonsterTypeIsAttackable); + registerMethod("MonsterType", "isConvinceable", LuaScriptInterface::luaMonsterTypeIsConvinceable); + registerMethod("MonsterType", "isSummonable", LuaScriptInterface::luaMonsterTypeIsSummonable); + registerMethod("MonsterType", "isIllusionable", LuaScriptInterface::luaMonsterTypeIsIllusionable); + registerMethod("MonsterType", "isHostile", LuaScriptInterface::luaMonsterTypeIsHostile); + registerMethod("MonsterType", "isPushable", LuaScriptInterface::luaMonsterTypeIsPushable); + + registerMethod("MonsterType", "canPushItems", LuaScriptInterface::luaMonsterTypeCanPushItems); + registerMethod("MonsterType", "canPushCreatures", LuaScriptInterface::luaMonsterTypeCanPushCreatures); + + registerMethod("MonsterType", "getName", LuaScriptInterface::luaMonsterTypeGetName); + registerMethod("MonsterType", "getNameDescription", LuaScriptInterface::luaMonsterTypeGetNameDescription); + + registerMethod("MonsterType", "getHealth", LuaScriptInterface::luaMonsterTypeGetHealth); + registerMethod("MonsterType", "getMaxHealth", LuaScriptInterface::luaMonsterTypeGetMaxHealth); + registerMethod("MonsterType", "getRunHealth", LuaScriptInterface::luaMonsterTypeGetRunHealth); + registerMethod("MonsterType", "getExperience", LuaScriptInterface::luaMonsterTypeGetExperience); + + registerMethod("MonsterType", "getCombatImmunities", LuaScriptInterface::luaMonsterTypeGetCombatImmunities); + registerMethod("MonsterType", "getConditionImmunities", LuaScriptInterface::luaMonsterTypeGetConditionImmunities); + + registerMethod("MonsterType", "getAttackList", LuaScriptInterface::luaMonsterTypeGetAttackList); + registerMethod("MonsterType", "getDefenseList", LuaScriptInterface::luaMonsterTypeGetDefenseList); + registerMethod("MonsterType", "getElementList", LuaScriptInterface::luaMonsterTypeGetElementList); + + registerMethod("MonsterType", "getVoices", LuaScriptInterface::luaMonsterTypeGetVoices); + registerMethod("MonsterType", "getLoot", LuaScriptInterface::luaMonsterTypeGetLoot); + registerMethod("MonsterType", "getCreatureEvents", LuaScriptInterface::luaMonsterTypeGetCreatureEvents); + + registerMethod("MonsterType", "getSummonList", LuaScriptInterface::luaMonsterTypeGetSummonList); + registerMethod("MonsterType", "getMaxSummons", LuaScriptInterface::luaMonsterTypeGetMaxSummons); + + registerMethod("MonsterType", "getArmor", LuaScriptInterface::luaMonsterTypeGetArmor); + registerMethod("MonsterType", "getDefense", LuaScriptInterface::luaMonsterTypeGetDefense); + registerMethod("MonsterType", "getOutfit", LuaScriptInterface::luaMonsterTypeGetOutfit); + registerMethod("MonsterType", "getRace", LuaScriptInterface::luaMonsterTypeGetRace); + registerMethod("MonsterType", "getCorpseId", LuaScriptInterface::luaMonsterTypeGetCorpseId); + registerMethod("MonsterType", "getManaCost", LuaScriptInterface::luaMonsterTypeGetManaCost); + registerMethod("MonsterType", "getBaseSpeed", LuaScriptInterface::luaMonsterTypeGetBaseSpeed); + registerMethod("MonsterType", "getLight", LuaScriptInterface::luaMonsterTypeGetLight); + + registerMethod("MonsterType", "getTargetDistance", LuaScriptInterface::luaMonsterTypeGetTargetDistance); + registerMethod("MonsterType", "getChangeTargetChance", LuaScriptInterface::luaMonsterTypeGetChangeTargetChance); + registerMethod("MonsterType", "getChangeTargetSpeed", LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed); + + // Party + registerClass("Party", "", nullptr); + registerMetaMethod("Party", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Party", "disband", LuaScriptInterface::luaPartyDisband); + + registerMethod("Party", "getLeader", LuaScriptInterface::luaPartyGetLeader); + registerMethod("Party", "setLeader", LuaScriptInterface::luaPartySetLeader); + + registerMethod("Party", "getMembers", LuaScriptInterface::luaPartyGetMembers); + registerMethod("Party", "getMemberCount", LuaScriptInterface::luaPartyGetMemberCount); + + registerMethod("Party", "getInvitees", LuaScriptInterface::luaPartyGetInvitees); + registerMethod("Party", "getInviteeCount", LuaScriptInterface::luaPartyGetInviteeCount); + + registerMethod("Party", "addInvite", LuaScriptInterface::luaPartyAddInvite); + registerMethod("Party", "removeInvite", LuaScriptInterface::luaPartyRemoveInvite); + + registerMethod("Party", "addMember", LuaScriptInterface::luaPartyAddMember); + registerMethod("Party", "removeMember", LuaScriptInterface::luaPartyRemoveMember); + + registerMethod("Party", "isSharedExperienceActive", LuaScriptInterface::luaPartyIsSharedExperienceActive); + registerMethod("Party", "isSharedExperienceEnabled", LuaScriptInterface::luaPartyIsSharedExperienceEnabled); + registerMethod("Party", "shareExperience", LuaScriptInterface::luaPartyShareExperience); + registerMethod("Party", "setSharedExperience", LuaScriptInterface::luaPartySetSharedExperience); +} + +#undef registerEnum +#undef registerEnumIn + +void LuaScriptInterface::registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction/* = nullptr*/) +{ + // className = {} + lua_newtable(luaState); + lua_pushvalue(luaState, -1); + lua_setglobal(luaState, className.c_str()); + int methods = lua_gettop(luaState); + + // methodsTable = {} + lua_newtable(luaState); + int methodsTable = lua_gettop(luaState); + + if (newFunction) { + // className.__call = newFunction + lua_pushcfunction(luaState, newFunction); + lua_setfield(luaState, methodsTable, "__call"); + } + + uint32_t parents = 0; + if (!baseClass.empty()) { + lua_getglobal(luaState, baseClass.c_str()); + lua_rawgeti(luaState, -1, 'p'); + parents = getNumber(luaState, -1) + 1; + lua_pop(luaState, 1); + lua_setfield(luaState, methodsTable, "__index"); + } + + // setmetatable(className, methodsTable) + lua_setmetatable(luaState, methods); + + // className.metatable = {} + luaL_newmetatable(luaState, className.c_str()); + int metatable = lua_gettop(luaState); + + // className.metatable.__metatable = className + lua_pushvalue(luaState, methods); + lua_setfield(luaState, metatable, "__metatable"); + + // className.metatable.__index = className + lua_pushvalue(luaState, methods); + lua_setfield(luaState, metatable, "__index"); + + // className.metatable['h'] = hash + lua_pushnumber(luaState, std::hash()(className)); + lua_rawseti(luaState, metatable, 'h'); + + // className.metatable['p'] = parents + lua_pushnumber(luaState, parents); + lua_rawseti(luaState, metatable, 'p'); + + // className.metatable['t'] = type + if (className == "Item") { + lua_pushnumber(luaState, LuaData_Item); + } else if (className == "Container") { + lua_pushnumber(luaState, LuaData_Container); + } else if (className == "Teleport") { + lua_pushnumber(luaState, LuaData_Teleport); + } else if (className == "Player") { + lua_pushnumber(luaState, LuaData_Player); + } else if (className == "Monster") { + lua_pushnumber(luaState, LuaData_Monster); + } else if (className == "Npc") { + lua_pushnumber(luaState, LuaData_Npc); + } else if (className == "Tile") { + lua_pushnumber(luaState, LuaData_Tile); + } else { + lua_pushnumber(luaState, LuaData_Unknown); + } + lua_rawseti(luaState, metatable, 't'); + + // pop className, className.metatable + lua_pop(luaState, 2); +} + +void LuaScriptInterface::registerTable(const std::string& tableName) +{ + // _G[tableName] = {} + lua_newtable(luaState); + lua_setglobal(luaState, tableName.c_str()); +} + +void LuaScriptInterface::registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func) +{ + // globalName.methodName = func + lua_getglobal(luaState, globalName.c_str()); + lua_pushcfunction(luaState, func); + lua_setfield(luaState, -2, methodName.c_str()); + + // pop globalName + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func) +{ + // className.metatable.methodName = func + luaL_getmetatable(luaState, className.c_str()); + lua_pushcfunction(luaState, func); + lua_setfield(luaState, -2, methodName.c_str()); + + // pop className.metatable + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerGlobalMethod(const std::string& functionName, lua_CFunction func) +{ + // _G[functionName] = func + lua_pushcfunction(luaState, func); + lua_setglobal(luaState, functionName.c_str()); +} + +void LuaScriptInterface::registerVariable(const std::string& tableName, const std::string& name, lua_Number value) +{ + // tableName.name = value + lua_getglobal(luaState, tableName.c_str()); + setField(luaState, name.c_str(), value); + + // pop tableName + lua_pop(luaState, 1); +} + +void LuaScriptInterface::registerGlobalVariable(const std::string& name, lua_Number value) +{ + // _G[name] = value + lua_pushnumber(luaState, value); + lua_setglobal(luaState, name.c_str()); +} + +void LuaScriptInterface::registerGlobalBoolean(const std::string& name, bool value) +{ + // _G[name] = value + pushBoolean(luaState, value); + lua_setglobal(luaState, name.c_str()); +} + +int LuaScriptInterface::luaGetPlayerFlagValue(lua_State* L) +{ + //getPlayerFlagValue(cid, flag) + Player* player = getPlayer(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellCount(lua_State* L) +{ + //getPlayerInstantSpellCount(cid) + Player* player = getPlayer(L, 1); + if (player) { + lua_pushnumber(L, g_spells->getInstantSpellCount(player)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellInfo(lua_State* L) +{ + //getPlayerInstantSpellInfo(cid, index) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t index = getNumber(L, 2); + InstantSpell* spell = g_spells->getInstantSpellByIndex(player, index); + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + lua_createtable(L, 0, 6); + setField(L, "name", spell->getName()); + setField(L, "words", spell->getWords()); + setField(L, "level", spell->getLevel()); + setField(L, "mlevel", spell->getMagicLevel()); + setField(L, "mana", spell->getManaCost(player)); + setField(L, "manapercent", spell->getManaPercent()); + return 1; +} + +int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) +{ + //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) + //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t itemId = getNumber(L, 2); + int32_t count = getNumber(L, 3, 1); + bool canDropOnMap = getBoolean(L, 4, true); + uint16_t subType = getNumber(L, 5, 1); + + const ItemType& it = Item::items[itemId]; + int32_t itemCount; + + auto parameters = lua_gettop(L); + if (parameters > 4) { + //subtype already supplied, count then is the amount + itemCount = std::max(1, count); + } else if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } else { + itemCount = 1; + } + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + uint16_t stackCount = subType; + if (it.stackable && stackCount > 100) { + stackCount = 100; + } + + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem, canDropOnMap); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = getScriptEnv()->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + return 1; + } + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoTileAddItemEx(lua_State* L) +{ + //doTileAddItemEx(pos, uid) + const Position& pos = getPosition(L, 1); + + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + uint32_t uid = getNumber(L, 2); + Item* item = getScriptEnv()->getItemByUID(uid); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, g_game.internalAddItem(tile, item)); + return 1; +} + +int LuaScriptInterface::luaDoCreateItem(lua_State* L) +{ + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + const Position& pos = getPosition(L, 3); + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + + int32_t itemCount = 1; + int32_t subType = 1; + + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(tile, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + return 1; + } + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoCreateItemEx(lua_State* L) +{ + //doCreateItemEx(itemid, count/subtype) + //Returns uid of the created item + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.stackable && count > 100) { + reportErrorFunc("Stack count cannot be higher than 100."); + count = 100; + } + + Item* newItem = Item::CreateItem(itemId, count); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + newItem->setParent(VirtualCylinder::virtualCylinder); + + ScriptEnvironment* env = getScriptEnv(); + env->addTempItem(newItem); + + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; +} + +int LuaScriptInterface::luaDebugPrint(lua_State* L) +{ + //debugPrint(text) + reportErrorFunc(getString(L, -1)); + return 0; +} + +int LuaScriptInterface::luaGetWorldTime(lua_State* L) +{ + //getWorldTime() + uint32_t time = g_game.getLightHour(); + lua_pushnumber(L, time); + return 1; +} + +int LuaScriptInterface::luaGetWorldLight(lua_State* L) +{ + //getWorldLight() + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); + return 2; +} + +int LuaScriptInterface::luaGetWorldUpTime(lua_State* L) +{ + //getWorldUpTime() + uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; + lua_pushnumber(L, uptime); + return 1; +} + +bool LuaScriptInterface::getArea(lua_State* L, std::list& list, uint32_t& rows) +{ + lua_pushnil(L); + for (rows = 0; lua_next(L, -2) != 0; ++rows) { + if (!isTable(L, -1)) { + return false; + } + + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + if (!isNumber(L, -1)) { + return false; + } + list.push_back(getNumber(L, -1)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + return (rows != 0); +} + +int LuaScriptInterface::luaCreateCombatArea(lua_State* L) +{ + //createCombatArea( {area}, {extArea} ) + ScriptEnvironment* env = getScriptEnv(); + if (env->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = g_luaEnvironment.createAreaObject(env->getScriptInterface()); + AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + + int parameters = lua_gettop(L); + if (parameters >= 2) { + uint32_t rowsExtArea; + std::list listExtArea; + if (!isTable(L, 2) || !getArea(L, listExtArea, rowsExtArea)) { + reportErrorFunc("Invalid extended area table."); + pushBoolean(L, false); + return 1; + } + area->setupExtArea(listExtArea, rowsExtArea); + } + + uint32_t rowsArea = 0; + std::list listArea; + if (!isTable(L, 1) || !getArea(L, listArea, rowsArea)) { + reportErrorFunc("Invalid area table."); + pushBoolean(L, false); + return 1; + } + + area->setupArea(listArea, rowsArea); + lua_pushnumber(L, areaId); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatHealth(lua_State* L) +{ + //doAreaCombatHealth(cid, type, pos, area, min, max, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 4); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatType_t combatType = getNumber(L, 2); + + CombatParams params; + params.combatType = combatType; + params.impactEffect = getNumber(L, 7); + + CombatDamage damage; + damage.type = combatType; + damage.value = normal_random(getNumber(L, 6), getNumber(L, 5)); + + Combat::doCombatHealth(creature, getPosition(L, 3), area, damage, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatHealth(lua_State* L) +{ + //doTargetCombatHealth(cid, target, type, min, max, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatType_t combatType = getNumber(L, 3); + + CombatParams params; + params.combatType = combatType; + params.impactEffect = getNumber(L, 6); + + CombatDamage damage; + damage.type = combatType; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + + Combat::doCombatHealth(creature, target, damage, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatMana(lua_State* L) +{ + //doAreaCombatMana(cid, pos, area, min, max, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 6); + + CombatDamage damage; + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + + Position pos = getPosition(L, 2); + Combat::doCombatMana(creature, pos, area, damage, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatMana(lua_State* L) +{ + //doTargetCombatMana(cid, target, min, max, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.impactEffect = getNumber(L, 5); + + CombatDamage damage; + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 3), getNumber(L, 4)); + + Combat::doCombatMana(creature, target, damage, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatCondition(lua_State* L) +{ + //doAreaCombatCondition(cid, pos, area, condition, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + const Condition* condition = getUserdata(L, 4); + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 5); + params.conditionList.emplace_front(condition); + Combat::doCombatCondition(creature, getPosition(L, 2), area, params); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatCondition(lua_State* L) +{ + //doTargetCombatCondition(cid, target, condition, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + const Condition* condition = getUserdata(L, 3); + if (!condition) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONDITION_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.impactEffect = getNumber(L, 4); + params.conditionList.emplace_front(condition); + Combat::doCombatCondition(creature, target, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoAreaCombatDispel(lua_State* L) +{ + //doAreaCombatDispel(cid, pos, area, type, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t areaId = getNumber(L, 3); + const AreaCombat* area = g_luaEnvironment.getAreaObject(areaId); + if (area || areaId == 0) { + CombatParams params; + params.impactEffect = getNumber(L, 5); + params.dispelType = getNumber(L, 4); + Combat::doCombatDispel(creature, getPosition(L, 2), area, params); + + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDoTargetCombatDispel(lua_State* L) +{ + //doTargetCombatDispel(cid, target, type, effect) + Creature* creature = getCreature(L, 1); + if (!creature && (!isNumber(L, 1) || getNumber(L, 1) != 0)) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + CombatParams params; + params.dispelType = getNumber(L, 3); + params.impactEffect = getNumber(L, 4); + Combat::doCombatDispel(creature, target, params); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) +{ + //doChallengeCreature(cid, target) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Creature* target = getCreature(L, 2); + if (!target) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + target->challengeCreature(creature); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaSetCreatureOutfit(lua_State* L) +{ + //doSetCreatureOutfit(cid, outfit, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Outfit_t outfit = getOutfit(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, outfit, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetMonsterOutfit(lua_State* L) +{ + //doSetMonsterOutfit(cid, name, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + std::string name = getString(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, name, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetItemOutfit(lua_State* L) +{ + //doSetItemOutfit(cid, item, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t item = getNumber(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, item, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaDoMoveCreature(lua_State* L) +{ + //doMoveCreature(cid, direction) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Direction direction = getNumber(L, 2); + if (direction > DIRECTION_LAST) { + reportErrorFunc("No valid direction"); + pushBoolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT); + lua_pushnumber(L, ret); + return 1; +} + +int LuaScriptInterface::luaIsValidUID(lua_State* L) +{ + //isValidUID(uid) + pushBoolean(L, getScriptEnv()->getThingByUID(getNumber(L, -1)) != nullptr); + return 1; +} + +int LuaScriptInterface::luaIsDepot(lua_State* L) +{ + //isDepot(uid) + Container* container = getScriptEnv()->getContainerByUID(getNumber(L, -1)); + pushBoolean(L, container && container->getDepotLocker()); + return 1; +} + +int LuaScriptInterface::luaIsMoveable(lua_State* L) +{ + //isMoveable(uid) + //isMovable(uid) + Thing* thing = getScriptEnv()->getThingByUID(getNumber(L, -1)); + pushBoolean(L, thing && thing->isPushable()); + return 1; +} + +int LuaScriptInterface::luaDoAddContainerItem(lua_State* L) +{ + //doAddContainerItem(uid, itemid, count/subtype) + uint32_t uid = getNumber(L, 1); + + ScriptEnvironment* env = getScriptEnv(); + Container* container = env->getContainerByUID(uid); + if (!container) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t itemId = getNumber(L, 2); + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + int32_t subType = 1; + uint32_t count = getNumber(L, 3, 1); + + if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(container, newItem); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + lua_pushnumber(L, env->addThing(newItem)); + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + } + return 1; + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaGetDepotId(lua_State* L) +{ + //getDepotId(uid) + uint32_t uid = getNumber(L, -1); + + Container* container = getScriptEnv()->getContainerByUID(uid); + if (!container) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CONTAINER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + DepotLocker* depotLocker = container->getDepotLocker(); + if (!depotLocker) { + reportErrorFunc("Depot not found"); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, depotLocker->getDepotId()); + return 1; +} + +int LuaScriptInterface::luaIsInArray(lua_State* L) +{ + //isInArray(array, value) + if (!isTable(L, 1)) { + pushBoolean(L, false); + return 1; + } + + lua_pushnil(L); + while (lua_next(L, 1)) { + if (lua_equal(L, 2, -1) != 0) { + pushBoolean(L, true); + return 1; + } + lua_pop(L, 1); + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoSetCreatureLight(lua_State* L) +{ + //doSetCreatureLight(cid, lightLevel, lightColor, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint16_t level = getNumber(L, 2); + uint16_t color = getNumber(L, 3); + uint32_t time = getNumber(L, 4); + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_LIGHT, time, level | (color << 8)); + creature->addCondition(condition); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaAddEvent(lua_State* L) +{ + //addEvent(callback, delay, ...) + lua_State* globalState = g_luaEnvironment.getLuaState(); + if (!globalState) { + reportErrorFunc("No valid script interface!"); + pushBoolean(L, false); + return 1; + } else if (globalState != L) { + lua_xmove(L, globalState, lua_gettop(L)); + } + + int parameters = lua_gettop(globalState); + if (!isFunction(globalState, -parameters)) { //-parameters means the first parameter from left to right + reportErrorFunc("callback parameter should be a function."); + pushBoolean(L, false); + return 1; + } + + if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS) || g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { + std::vector> indexes; + for (int i = 3; i <= parameters; ++i) { + if (lua_getmetatable(globalState, i) == 0) { + continue; + } + lua_rawgeti(L, -1, 't'); + + LuaDataType type = getNumber(L, -1); + if (type != LuaData_Unknown && type != LuaData_Tile) { + indexes.push_back({i, type}); + } + lua_pop(globalState, 2); + } + + if (!indexes.empty()) { + if (g_config.getBoolean(ConfigManager::WARN_UNSAFE_SCRIPTS)) { + bool plural = indexes.size() > 1; + + std::string warningString = "Argument"; + if (plural) { + warningString += 's'; + } + + for (const auto& entry : indexes) { + if (entry == indexes.front()) { + warningString += ' '; + } else if (entry == indexes.back()) { + warningString += " and "; + } else { + warningString += ", "; + } + warningString += '#'; + warningString += std::to_string(entry.first); + } + + if (plural) { + warningString += " are unsafe"; + } else { + warningString += " is unsafe"; + } + + reportErrorFunc(warningString); + } + + if (g_config.getBoolean(ConfigManager::CONVERT_UNSAFE_SCRIPTS)) { + for (const auto& entry : indexes) { + switch (entry.second) { + case LuaData_Item: + case LuaData_Container: + case LuaData_Teleport: { + lua_getglobal(globalState, "Item"); + lua_getfield(globalState, -1, "getMovementId"); + break; + } + case LuaData_Player: + case LuaData_Monster: + case LuaData_Npc: { + lua_getglobal(globalState, "Creature"); + lua_getfield(globalState, -1, "getId"); + break; + } + default: + break; + } + lua_replace(globalState, -2); + lua_pushvalue(globalState, entry.first); + lua_call(globalState, 1, 1); + lua_replace(globalState, entry.first); + } + } + } + } + + LuaTimerEventDesc eventDesc; + for (int i = 0; i < parameters - 2; ++i) { //-2 because addEvent needs at least two parameters + eventDesc.parameters.push_back(luaL_ref(globalState, LUA_REGISTRYINDEX)); + } + + uint32_t delay = std::max(100, getNumber(globalState, 2)); + lua_pop(globalState, 1); + + eventDesc.function = luaL_ref(globalState, LUA_REGISTRYINDEX); + eventDesc.scriptId = getScriptEnv()->getScriptId(); + + auto& lastTimerEventId = g_luaEnvironment.lastEventTimerId; + eventDesc.eventId = g_scheduler.addEvent(createSchedulerTask( + delay, std::bind(&LuaEnvironment::executeTimerEvent, &g_luaEnvironment, lastTimerEventId) + )); + + g_luaEnvironment.timerEvents.emplace(lastTimerEventId, std::move(eventDesc)); + lua_pushnumber(L, lastTimerEventId++); + return 1; +} + +int LuaScriptInterface::luaStopEvent(lua_State* L) +{ + //stopEvent(eventid) + lua_State* globalState = g_luaEnvironment.getLuaState(); + if (!globalState) { + reportErrorFunc("No valid script interface!"); + pushBoolean(L, false); + return 1; + } + + uint32_t eventId = getNumber(L, 1); + + auto& timerEvents = g_luaEnvironment.timerEvents; + auto it = timerEvents.find(eventId); + if (it == timerEvents.end()) { + pushBoolean(L, false); + return 1; + } + + LuaTimerEventDesc timerEventDesc = std::move(it->second); + timerEvents.erase(it); + + g_scheduler.stopEvent(timerEventDesc.eventId); + luaL_unref(globalState, LUA_REGISTRYINDEX, timerEventDesc.function); + + for (auto parameter : timerEventDesc.parameters) { + luaL_unref(globalState, LUA_REGISTRYINDEX, parameter); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGetCreatureCondition(lua_State* L) +{ + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + ConditionType_t condition = getNumber(L, 2); + uint32_t subId = getNumber(L, 3, 0); + pushBoolean(L, creature->hasCondition(condition, subId)); + return 1; +} + +int LuaScriptInterface::luaSaveServer(lua_State* L) +{ + g_game.saveGameState(); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCleanMap(lua_State* L) +{ + lua_pushnumber(L, g_game.map.clean()); + return 1; +} + +int LuaScriptInterface::luaIsInWar(lua_State* L) +{ + //isInWar(cid, target) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Player* targetPlayer = getPlayer(L, 2); + if (!targetPlayer) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + pushBoolean(L, player->isInWar(targetPlayer)); + return 1; +} + +int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) +{ + //getWaypointPositionByName(name) + auto& waypoints = g_game.map.waypoints; + + auto it = waypoints.find(getString(L, -1)); + if (it != waypoints.end()) { + pushPosition(L, it->second); + } else { + pushBoolean(L, false); + } + return 1; +} + +std::string LuaScriptInterface::escapeString(const std::string& string) +{ + std::string s = string; + replaceString(s, "\\", "\\\\"); + replaceString(s, "\"", "\\\""); + replaceString(s, "'", "\\'"); + replaceString(s, "[[", "\\[["); + return s; +} + +#ifndef LUAJIT_VERSION +const luaL_Reg LuaScriptInterface::luaBitReg[] = { + //{"tobit", LuaScriptInterface::luaBitToBit}, + {"bnot", LuaScriptInterface::luaBitNot}, + {"band", LuaScriptInterface::luaBitAnd}, + {"bor", LuaScriptInterface::luaBitOr}, + {"bxor", LuaScriptInterface::luaBitXor}, + {"lshift", LuaScriptInterface::luaBitLeftShift}, + {"rshift", LuaScriptInterface::luaBitRightShift}, + //{"arshift", LuaScriptInterface::luaBitArithmeticalRightShift}, + //{"rol", LuaScriptInterface::luaBitRotateLeft}, + //{"ror", LuaScriptInterface::luaBitRotateRight}, + //{"bswap", LuaScriptInterface::luaBitSwapEndian}, + //{"tohex", LuaScriptInterface::luaBitToHex}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaBitNot(lua_State* L) +{ + lua_pushnumber(L, ~getNumber(L, -1)); + return 1; +} + +#define MULTIOP(name, op) \ +int LuaScriptInterface::luaBit##name(lua_State* L) \ +{ \ + int n = lua_gettop(L); \ + uint32_t w = getNumber(L, -1); \ + for (int i = 1; i < n; ++i) \ + w op getNumber(L, i); \ + lua_pushnumber(L, w); \ + return 1; \ +} + +MULTIOP(And, &= ) +MULTIOP(Or, |= ) +MULTIOP(Xor, ^= ) + +#define SHIFTOP(name, op) \ +int LuaScriptInterface::luaBit##name(lua_State* L) \ +{ \ + uint32_t n1 = getNumber(L, 1), n2 = getNumber(L, 2); \ + lua_pushnumber(L, (n1 op n2)); \ + return 1; \ +} + +SHIFTOP(LeftShift, << ) +SHIFTOP(RightShift, >> ) +#endif + +const luaL_Reg LuaScriptInterface::luaConfigManagerTable[] = { + {"getString", LuaScriptInterface::luaConfigManagerGetString}, + {"getNumber", LuaScriptInterface::luaConfigManagerGetNumber}, + {"getBoolean", LuaScriptInterface::luaConfigManagerGetBoolean}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaConfigManagerGetString(lua_State* L) +{ + pushString(L, g_config.getString(getNumber(L, -1))); + return 1; +} + +int LuaScriptInterface::luaConfigManagerGetNumber(lua_State* L) +{ + lua_pushnumber(L, g_config.getNumber(getNumber(L, -1))); + return 1; +} + +int LuaScriptInterface::luaConfigManagerGetBoolean(lua_State* L) +{ + pushBoolean(L, g_config.getBoolean(getNumber(L, -1))); + return 1; +} + +const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { + {"query", LuaScriptInterface::luaDatabaseExecute}, + {"asyncQuery", LuaScriptInterface::luaDatabaseAsyncExecute}, + {"storeQuery", LuaScriptInterface::luaDatabaseStoreQuery}, + {"asyncStoreQuery", LuaScriptInterface::luaDatabaseAsyncStoreQuery}, + {"escapeString", LuaScriptInterface::luaDatabaseEscapeString}, + {"escapeBlob", LuaScriptInterface::luaDatabaseEscapeBlob}, + {"lastInsertId", LuaScriptInterface::luaDatabaseLastInsertId}, + {"tableExists", LuaScriptInterface::luaDatabaseTableExists}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaDatabaseExecute(lua_State* L) +{ + pushBoolean(L, Database::getInstance()->executeQuery(getString(L, -1))); + return 1; +} + +int LuaScriptInterface::luaDatabaseAsyncExecute(lua_State* L) +{ + std::function callback; + if (lua_gettop(L) > 1) { + int32_t ref = luaL_ref(L, LUA_REGISTRYINDEX); + auto scriptId = getScriptEnv()->getScriptId(); + callback = [ref, scriptId](DBResult_ptr, bool success) { + lua_State* luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return; + } + + if (!LuaScriptInterface::reserveScriptEnv()) { + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + return; + } + + lua_rawgeti(luaState, LUA_REGISTRYINDEX, ref); + pushBoolean(luaState, success); + auto env = getScriptEnv(); + env->setScriptId(scriptId, &g_luaEnvironment); + g_luaEnvironment.callFunction(1); + + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + }; + } + g_databaseTasks.addTask(getString(L, -1), callback); + return 0; +} + +int LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) +{ + if (DBResult_ptr res = Database::getInstance()->storeQuery(getString(L, -1))) { + lua_pushnumber(L, ScriptEnvironment::addResult(res)); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaDatabaseAsyncStoreQuery(lua_State* L) +{ + std::function callback; + if (lua_gettop(L) > 1) { + int32_t ref = luaL_ref(L, LUA_REGISTRYINDEX); + auto scriptId = getScriptEnv()->getScriptId(); + callback = [ref, scriptId](DBResult_ptr result, bool) { + lua_State* luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return; + } + + if (!LuaScriptInterface::reserveScriptEnv()) { + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + return; + } + + lua_rawgeti(luaState, LUA_REGISTRYINDEX, ref); + if (result) { + lua_pushnumber(luaState, ScriptEnvironment::addResult(result)); + } else { + pushBoolean(luaState, false); + } + auto env = getScriptEnv(); + env->setScriptId(scriptId, &g_luaEnvironment); + g_luaEnvironment.callFunction(1); + + luaL_unref(luaState, LUA_REGISTRYINDEX, ref); + }; + } + g_databaseTasks.addTask(getString(L, -1), callback, true); + return 0; +} + +int LuaScriptInterface::luaDatabaseEscapeString(lua_State* L) +{ + pushString(L, Database::getInstance()->escapeString(getString(L, -1))); + return 1; +} + +int LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) +{ + uint32_t length = getNumber(L, 2); + pushString(L, Database::getInstance()->escapeBlob(getString(L, 1).c_str(), length)); + return 1; +} + +int LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) +{ + lua_pushnumber(L, Database::getInstance()->getLastInsertId()); + return 1; +} + +int LuaScriptInterface::luaDatabaseTableExists(lua_State* L) +{ + pushBoolean(L, DatabaseManager::tableExists(getString(L, -1))); + return 1; +} + +const luaL_Reg LuaScriptInterface::luaResultTable[] = { + {"getNumber", LuaScriptInterface::luaResultGetNumber}, + {"getString", LuaScriptInterface::luaResultGetString}, + {"getStream", LuaScriptInterface::luaResultGetStream}, + {"next", LuaScriptInterface::luaResultNext}, + {"free", LuaScriptInterface::luaResultFree}, + {nullptr, nullptr} +}; + +int LuaScriptInterface::luaResultGetNumber(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + const std::string& s = getString(L, 2); + lua_pushnumber(L, res->getNumber(s)); + return 1; +} + +int LuaScriptInterface::luaResultGetString(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + const std::string& s = getString(L, 2); + pushString(L, res->getString(s)); + return 1; +} + +int LuaScriptInterface::luaResultGetStream(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, 1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + unsigned long length; + const char* stream = res->getStream(getString(L, 2), length); + lua_pushlstring(L, stream, length); + lua_pushnumber(L, length); + return 2; +} + +int LuaScriptInterface::luaResultNext(lua_State* L) +{ + DBResult_ptr res = ScriptEnvironment::getResultByID(getNumber(L, -1)); + if (!res) { + pushBoolean(L, false); + return 1; + } + + pushBoolean(L, res->next()); + return 1; +} + +int LuaScriptInterface::luaResultFree(lua_State* L) +{ + pushBoolean(L, ScriptEnvironment::removeResult(getNumber(L, -1))); + return 1; +} + +// Userdata +int LuaScriptInterface::luaUserdataCompare(lua_State* L) +{ + // userdataA == userdataB + pushBoolean(L, getUserdata(L, 1) == getUserdata(L, 2)); + return 1; +} + +// _G +int LuaScriptInterface::luaIsType(lua_State* L) +{ + // isType(derived, base) + lua_getmetatable(L, -2); + lua_getmetatable(L, -2); + + lua_rawgeti(L, -2, 'p'); + uint_fast8_t parentsB = getNumber(L, 1); + + lua_rawgeti(L, -3, 'h'); + size_t hashB = getNumber(L, 1); + + lua_rawgeti(L, -3, 'p'); + uint_fast8_t parentsA = getNumber(L, 1); + for (uint_fast8_t i = parentsA; i < parentsB; ++i) { + lua_getfield(L, -3, "__index"); + lua_replace(L, -4); + } + + lua_rawgeti(L, -4, 'h'); + size_t hashA = getNumber(L, 1); + + pushBoolean(L, hashA == hashB); + return 1; +} + +int LuaScriptInterface::luaRawGetMetatable(lua_State* L) +{ + // rawgetmetatable(metatableName) + luaL_getmetatable(L, getString(L, 1).c_str()); + return 1; +} + +// os +int LuaScriptInterface::luaSystemTime(lua_State* L) +{ + // os.mtime() + lua_pushnumber(L, OTSYS_TIME()); + return 1; +} + +// table +int LuaScriptInterface::luaTableCreate(lua_State* L) +{ + // table.create(arrayLength, keyLength) + lua_createtable(L, getNumber(L, 1), getNumber(L, 2)); + return 1; +} + +// Game +int LuaScriptInterface::luaGameGetSpectators(lua_State* L) +{ + // Game.getSpectators(position[, multifloor = false[, onlyPlayer = false[, minRangeX = 0[, maxRangeX = 0[, minRangeY = 0[, maxRangeY = 0]]]]]]) + const Position& position = getPosition(L, 1); + bool multifloor = getBoolean(L, 2, false); + bool onlyPlayers = getBoolean(L, 3, false); + int32_t minRangeX = getNumber(L, 4, 0); + int32_t maxRangeX = getNumber(L, 5, 0); + int32_t minRangeY = getNumber(L, 6, 0); + int32_t maxRangeY = getNumber(L, 7, 0); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, multifloor, onlyPlayers, minRangeX, maxRangeX, minRangeY, maxRangeY); + + lua_createtable(L, spectators.size(), 0); + + int index = 0; + for (Creature* creature : spectators) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetPlayers(lua_State* L) +{ + // Game.getPlayers() + lua_createtable(L, g_game.getPlayersOnline(), 0); + + int index = 0; + for (const auto& playerEntry : g_game.getPlayers()) { + pushUserdata(L, playerEntry.second); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameLoadMap(lua_State* L) +{ + // Game.loadMap(path) + const std::string& path = getString(L, 1); + g_dispatcher.addTask(createTask(std::bind(&Game::loadMap, &g_game, path))); + return 0; +} + +int LuaScriptInterface::luaGameGetExperienceStage(lua_State* L) +{ + // Game.getExperienceStage(level) + uint32_t level = getNumber(L, 1); + lua_pushnumber(L, g_game.getExperienceStage(level)); + return 1; +} + +int LuaScriptInterface::luaGameGetMonsterCount(lua_State* L) +{ + // Game.getMonsterCount() + lua_pushnumber(L, g_game.getMonstersOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetPlayerCount(lua_State* L) +{ + // Game.getPlayerCount() + lua_pushnumber(L, g_game.getPlayersOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetNpcCount(lua_State* L) +{ + // Game.getNpcCount() + lua_pushnumber(L, g_game.getNpcsOnline()); + return 1; +} + +int LuaScriptInterface::luaGameGetTowns(lua_State* L) +{ + // Game.getTowns() + const auto& towns = g_game.map.towns.getTowns(); + lua_createtable(L, towns.size(), 0); + + int index = 0; + for (auto townEntry : towns) { + pushUserdata(L, townEntry.second); + setMetatable(L, -1, "Town"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetHouses(lua_State* L) +{ + // Game.getHouses() + const auto& houses = g_game.map.houses.getHouses(); + lua_createtable(L, houses.size(), 0); + + int index = 0; + for (auto houseEntry : houses) { + pushUserdata(L, houseEntry.second); + setMetatable(L, -1, "House"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGameGetGameState(lua_State* L) +{ + // Game.getGameState() + lua_pushnumber(L, g_game.getGameState()); + return 1; +} + +int LuaScriptInterface::luaGameSetGameState(lua_State* L) +{ + // Game.setGameState(state) + GameState_t state = getNumber(L, 1); + g_game.setGameState(state); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGameGetWorldType(lua_State* L) +{ + // Game.getWorldType() + lua_pushnumber(L, g_game.getWorldType()); + return 1; +} + +int LuaScriptInterface::luaGameSetWorldType(lua_State* L) +{ + // Game.setWorldType(type) + WorldType_t type = getNumber(L, 1); + g_game.setWorldType(type); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaGameGetReturnMessage(lua_State* L) +{ + // Game.getReturnMessage(value) + ReturnValue value = getNumber(L, 1); + pushString(L, getReturnMessage(value)); + return 1; +} + +int LuaScriptInterface::luaGameCreateItem(lua_State* L) +{ + // Game.createItem(itemId[, count[, position]]) + uint16_t count = getNumber(L, 2, 1); + uint16_t id; + if (isNumber(L, 1)) { + id = getNumber(L, 1); + } else { + id = Item::items.getItemIdByName(getString(L, 1)); + if (id == 0) { + lua_pushnil(L); + return 1; + } + } + + const ItemType& it = Item::items[id]; + if (it.stackable) { + count = std::min(count, 100); + } + + Item* item = Item::CreateItem(id, count); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) >= 3) { + const Position& position = getPosition(L, 3); + Tile* tile = g_game.map.getTile(position); + if (!tile) { + delete item; + lua_pushnil(L); + return 1; + } + + g_game.internalAddItem(tile, item, INDEX_WHEREEVER, FLAG_NOLIMIT); + } else { + getScriptEnv()->addTempItem(item); + item->setParent(VirtualCylinder::virtualCylinder); + } + + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; +} + +int LuaScriptInterface::luaGameCreateContainer(lua_State* L) +{ + // Game.createContainer(itemId, size[, position]) + uint16_t size = getNumber(L, 2); + uint16_t id; + if (isNumber(L, 1)) { + id = getNumber(L, 1); + } else { + id = Item::items.getItemIdByName(getString(L, 1)); + if (id == 0) { + lua_pushnil(L); + return 1; + } + } + + Container* container = Item::CreateItemAsContainer(id, size); + if (!container) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) >= 3) { + const Position& position = getPosition(L, 3); + Tile* tile = g_game.map.getTile(position); + if (!tile) { + delete container; + lua_pushnil(L); + return 1; + } + + g_game.internalAddItem(tile, container, INDEX_WHEREEVER, FLAG_NOLIMIT); + } else { + getScriptEnv()->addTempItem(container); + container->setParent(VirtualCylinder::virtualCylinder); + } + + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + return 1; +} + +int LuaScriptInterface::luaGameCreateMonster(lua_State* L) +{ + // Game.createMonster(monsterName, position[, extended = false[, force = false]]) + Monster* monster = Monster::createMonster(getString(L, 1)); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + bool extended = getBoolean(L, 3, false); + bool force = getBoolean(L, 4, false); + if (g_game.placeCreature(monster, position, extended, force)) { + pushUserdata(L, monster); + setMetatable(L, -1, "Monster"); + } else { + delete monster; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGameCreateNpc(lua_State* L) +{ + // Game.createNpc(npcName, position[, extended = false[, force = false]]) + Npc* npc = Npc::createNpc(getString(L, 1)); + if (!npc) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + bool extended = getBoolean(L, 3, false); + bool force = getBoolean(L, 4, false); + if (g_game.placeCreature(npc, position, extended, force)) { + pushUserdata(L, npc); + setMetatable(L, -1, "Npc"); + } else { + delete npc; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGameCreateTile(lua_State* L) +{ + // Game.createTile(x, y, z[, isDynamic = false]) + // Game.createTile(position[, isDynamic = false]) + Position position; + bool isDynamic; + if (isTable(L, 1)) { + position = getPosition(L, 1); + isDynamic = getBoolean(L, 2, false); + } else { + position.x = getNumber(L, 1); + position.y = getNumber(L, 2); + position.z = getNumber(L, 3); + isDynamic = getBoolean(L, 4, false); + } + + Tile* tile = g_game.map.getTile(position); + if (!tile) { + if (isDynamic) { + tile = new DynamicTile(position.x, position.y, position.z); + } else { + tile = new StaticTile(position.x, position.y, position.z); + } + + g_game.map.setTile(position, tile); + } + + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + return 1; +} + +int LuaScriptInterface::luaGameStartRaid(lua_State* L) +{ + // Game.startRaid(raidName) + const std::string& raidName = getString(L, 1); + + Raid* raid = g_game.raids.getRaidByName(raidName); + if (!raid || !raid->isLoaded()) { + lua_pushnil(L); + return 1; + } + + if (g_game.raids.getRunning()) { + lua_pushnil(L); + return 1; + } + + g_game.raids.setRunning(raid); + raid->startRaid(); + lua_pushnumber(L, RETURNVALUE_NOERROR); + return 1; +} + +// Variant +int LuaScriptInterface::luaVariantCreate(lua_State* L) +{ + // Variant(number or string or position or thing) + LuaVariant variant; + if (isUserdata(L, 2)) { + if (Thing* thing = getThing(L, 2)) { + variant.type = VARIANT_TARGETPOSITION; + variant.pos = thing->getPosition(); + } + } else if (isTable(L, 2)) { + variant.type = VARIANT_POSITION; + variant.pos = getPosition(L, 2); + } else if (isNumber(L, 2)) { + variant.type = VARIANT_NUMBER; + variant.number = getNumber(L, 2); + } else if (isString(L, 2)) { + variant.type = VARIANT_STRING; + variant.text = getString(L, 2); + } + pushVariant(L, variant); + return 1; +} + +int LuaScriptInterface::luaVariantGetNumber(lua_State* L) +{ + // Variant:getNumber() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_NUMBER) { + lua_pushnumber(L, variant.number); + } else { + lua_pushnumber(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaVariantGetString(lua_State* L) +{ + // Variant:getString() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_STRING) { + pushString(L, variant.text); + } else { + pushString(L, std::string()); + } + return 1; +} + +int LuaScriptInterface::luaVariantGetPosition(lua_State* L) +{ + // Variant:getPosition() + const LuaVariant& variant = getVariant(L, 1); + if (variant.type == VARIANT_POSITION || variant.type == VARIANT_TARGETPOSITION) { + pushPosition(L, variant.pos); + } else { + pushPosition(L, Position()); + } + return 1; +} + +// Position +int LuaScriptInterface::luaPositionCreate(lua_State* L) +{ + // Position([x = 0[, y = 0[, z = 0[, stackpos = 0]]]]) + // Position([position]) + if (lua_gettop(L) <= 1) { + pushPosition(L, Position()); + return 1; + } + + int32_t stackpos; + if (isTable(L, 2)) { + const Position& position = getPosition(L, 2, stackpos); + pushPosition(L, position, stackpos); + } else { + uint16_t x = getNumber(L, 2, 0); + uint16_t y = getNumber(L, 3, 0); + uint8_t z = getNumber(L, 4, 0); + stackpos = getNumber(L, 5, 0); + + pushPosition(L, Position(x, y, z), stackpos); + } + return 1; +} + +int LuaScriptInterface::luaPositionAdd(lua_State* L) +{ + // positionValue = position + positionEx + int32_t stackpos; + const Position& position = getPosition(L, 1, stackpos); + + Position positionEx; + if (stackpos == 0) { + positionEx = getPosition(L, 2, stackpos); + } else { + positionEx = getPosition(L, 2); + } + + pushPosition(L, position + positionEx, stackpos); + return 1; +} + +int LuaScriptInterface::luaPositionSub(lua_State* L) +{ + // positionValue = position - positionEx + int32_t stackpos; + const Position& position = getPosition(L, 1, stackpos); + + Position positionEx; + if (stackpos == 0) { + positionEx = getPosition(L, 2, stackpos); + } else { + positionEx = getPosition(L, 2); + } + + pushPosition(L, position - positionEx, stackpos); + return 1; +} + +int LuaScriptInterface::luaPositionCompare(lua_State* L) +{ + // position == positionEx + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + pushBoolean(L, position == positionEx); + return 1; +} + +int LuaScriptInterface::luaPositionGetDistance(lua_State* L) +{ + // position:getDistance(positionEx) + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + lua_pushnumber(L, std::max( + std::max( + std::abs(Position::getDistanceX(position, positionEx)), + std::abs(Position::getDistanceY(position, positionEx)) + ), + std::abs(Position::getDistanceZ(position, positionEx)) + )); + return 1; +} + +int LuaScriptInterface::luaPositionIsSightClear(lua_State* L) +{ + // position:isSightClear(positionEx[, sameFloor = true]) + bool sameFloor = getBoolean(L, 3, true); + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + pushBoolean(L, g_game.isSightClear(position, positionEx, sameFloor)); + return 1; +} + +int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) +{ + // position:sendMagicEffect(magicEffect[, player = nullptr]) + SpectatorVec list; + if (lua_gettop(L) >= 3) { + Player* player = getPlayer(L, 3); + if (player) { + list.insert(player); + } + } + + MagicEffectClasses magicEffect = getNumber(L, 2); + const Position& position = getPosition(L, 1); + if (!list.empty()) { + Game::addMagicEffect(list, position, magicEffect); + } else { + g_game.addMagicEffect(position, magicEffect); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) +{ + // position:sendDistanceEffect(positionEx, distanceEffect[, player = nullptr]) + SpectatorVec list; + if (lua_gettop(L) >= 4) { + Player* player = getPlayer(L, 4); + if (player) { + list.insert(player); + } + } + + ShootType_t distanceEffect = getNumber(L, 3); + const Position& positionEx = getPosition(L, 2); + const Position& position = getPosition(L, 1); + if (!list.empty()) { + Game::addDistanceEffect(list, position, positionEx, distanceEffect); + } else { + g_game.addDistanceEffect(position, positionEx, distanceEffect); + } + + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPositionSendMonsterSay(lua_State * L) +{ + // position:sendMonsterSay(text) + const std::string& text = getString(L, 2); + const Position& position = getPosition(L, 1); + g_game.addMonsterSayText(position, text); + pushBoolean(L, true); + return 1; +} + +// Tile +int LuaScriptInterface::luaTileCreate(lua_State* L) +{ + // Tile(x, y, z) + // Tile(position) + Tile* tile; + if (isTable(L, 2)) { + tile = g_game.map.getTile(getPosition(L, 2)); + } else { + uint8_t z = getNumber(L, 4); + uint16_t y = getNumber(L, 3); + uint16_t x = getNumber(L, 2); + tile = g_game.map.getTile(x, y, z); + } + + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetPosition(lua_State* L) +{ + // tile:getPosition() + Tile* tile = getUserdata(L, 1); + if (tile) { + pushPosition(L, tile->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetGround(lua_State* L) +{ + // tile:getGround() + Tile* tile = getUserdata(L, 1); + if (tile && tile->getGround()) { + pushUserdata(L, tile->getGround()); + setItemMetatable(L, -1, tile->getGround()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThing(lua_State* L) +{ + // tile:getThing(index) + int32_t index = getNumber(L, 2); + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = tile->getThing(index); + if (!thing) { + lua_pushnil(L); + return 1; + } + + if (Creature* creature = thing->getCreature()) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else if (Item* item = thing->getItem()) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThingCount(lua_State* L) +{ + // tile:getThingCount() + Tile* tile = getUserdata(L, 1); + if (tile) { + lua_pushnumber(L, tile->getThingCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopVisibleThing(lua_State* L) +{ + // tile:getTopVisibleThing(creature) + Creature* creature = getCreature(L, 2); + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = tile->getTopVisibleThing(creature); + if (!thing) { + lua_pushnil(L); + return 1; + } + + if (Creature* visibleCreature = thing->getCreature()) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else if (Item* visibleItem = thing->getItem()) { + pushUserdata(L, visibleItem); + setItemMetatable(L, -1, visibleItem); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopTopItem(lua_State* L) +{ + // tile:getTopTopItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getTopTopItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopDownItem(lua_State* L) +{ + // tile:getTopDownItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getTopDownItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetFieldItem(lua_State* L) +{ + // tile:getFieldItem() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item = tile->getFieldItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemById(lua_State* L) +{ + // tile:getItemById(itemId[, subType = -1]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + int32_t subType = getNumber(L, 3, -1); + + Item* item = g_game.findItemOfType(tile, itemId, false, subType); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemByType(lua_State* L) +{ + // tile:getItemByType(itemType) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + bool found; + + ItemTypes_t itemType = getNumber(L, 2); + switch (itemType) { + case ITEM_TYPE_TELEPORT: + found = tile->hasFlag(TILESTATE_TELEPORT); + break; + case ITEM_TYPE_MAGICFIELD: + found = tile->hasFlag(TILESTATE_MAGICFIELD); + break; + case ITEM_TYPE_MAILBOX: + found = tile->hasFlag(TILESTATE_MAILBOX); + break; + case ITEM_TYPE_BED: + found = tile->hasFlag(TILESTATE_BED); + break; + case ITEM_TYPE_DEPOT: + found = tile->hasFlag(TILESTATE_DEPOT); + break; + default: + found = true; + break; + } + + if (!found) { + lua_pushnil(L); + return 1; + } + + if (Item* item = tile->getGround()) { + const ItemType& it = Item::items[item->getID()]; + if (it.type == itemType) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; + } + } + + if (const TileItemVector* items = tile->getItemList()) { + for (Item* item : *items) { + const ItemType& it = Item::items[item->getID()]; + if (it.type == itemType) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; + } + } + } + + lua_pushnil(L); + return 1; +} + +int LuaScriptInterface::luaTileGetItemByTopOrder(lua_State* L) +{ + // tile:getItemByTopOrder(topOrder) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + int32_t topOrder = getNumber(L, 2); + + Item* item = tile->getItemByTopOrder(topOrder); + if (!item) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, item); + setItemMetatable(L, -1, item); + return 1; +} + +int LuaScriptInterface::luaTileGetItemCountById(lua_State* L) +{ + // tile:getItemCountById(itemId[, subType = -1]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + int32_t subType = getNumber(L, 3, -1); + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + lua_pushnumber(L, tile->getItemTypeCount(itemId, subType)); + return 1; +} + +int LuaScriptInterface::luaTileGetBottomCreature(lua_State* L) +{ + // tile:getBottomCreature() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + const Creature* creature = tile->getBottomCreature(); + if (!creature) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + return 1; +} + +int LuaScriptInterface::luaTileGetTopCreature(lua_State* L) +{ + // tile:getTopCreature() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = tile->getTopCreature(); + if (!creature) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + return 1; +} + +int LuaScriptInterface::luaTileGetBottomVisibleCreature(lua_State* L) +{ + // tile:getBottomVisibleCreature(creature) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Creature* visibleCreature = tile->getBottomVisibleCreature(creature); + if (visibleCreature) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopVisibleCreature(lua_State* L) +{ + // tile:getTopVisibleCreature(creature) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* visibleCreature = tile->getTopVisibleCreature(creature); + if (visibleCreature) { + pushUserdata(L, visibleCreature); + setCreatureMetatable(L, -1, visibleCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItems(lua_State* L) +{ + // tile:getItems() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + TileItemVector* itemVector = tile->getItemList(); + if (!itemVector) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, itemVector->size(), 0); + + int index = 0; + for (Item* item : *itemVector) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaTileGetItemCount(lua_State* L) +{ + // tile:getItemCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getItemCount()); + return 1; +} + +int LuaScriptInterface::luaTileGetDownItemCount(lua_State* L) +{ + // tile:getDownItemCount() + Tile* tile = getUserdata(L, 1); + if (tile) { + lua_pushnumber(L, tile->getDownItemCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetTopItemCount(lua_State* L) +{ + // tile:getTopItemCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getTopItemCount()); + return 1; +} + +int LuaScriptInterface::luaTileGetCreatures(lua_State* L) +{ + // tile:getCreatures() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + CreatureVector* creatureVector = tile->getCreatures(); + if (!creatureVector) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creatureVector->size(), 0); + + int index = 0; + for (Creature* creature : *creatureVector) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaTileGetCreatureCount(lua_State* L) +{ + // tile:getCreatureCount() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, tile->getCreatureCount()); + return 1; +} + +int LuaScriptInterface::luaTileHasProperty(lua_State* L) +{ + // tile:hasProperty(property[, item]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Item* item; + if (lua_gettop(L) >= 3) { + item = getUserdata(L, 3); + } else { + item = nullptr; + } + + ITEMPROPERTY property = getNumber(L, 2); + if (item) { + pushBoolean(L, tile->hasProperty(item, property)); + } else { + pushBoolean(L, tile->hasProperty(property)); + } + return 1; +} + +int LuaScriptInterface::luaTileGetThingIndex(lua_State* L) +{ + // tile:getThingIndex(thing) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = getThing(L, 2); + if (thing) { + lua_pushnumber(L, tile->getThingIndex(thing)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileHasFlag(lua_State* L) +{ + // tile:hasFlag(flag) + Tile* tile = getUserdata(L, 1); + if (tile) { + tileflags_t flag = getNumber(L, 2); + pushBoolean(L, tile->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileQueryAdd(lua_State* L) +{ + // tile:queryAdd(thing[, flags]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + Thing* thing = getThing(L, 2); + if (thing) { + uint32_t flags = getNumber(L, 3, 0); + lua_pushnumber(L, tile->queryAdd(0, *thing, 1, flags)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileGetHouse(lua_State* L) +{ + // tile:getHouse() + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + if (HouseTile* houseTile = dynamic_cast(tile)) { + pushUserdata(L, houseTile->getHouse()); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +// NetworkMessage +int LuaScriptInterface::luaNetworkMessageCreate(lua_State* L) +{ + // NetworkMessage() + pushUserdata(L, new NetworkMessage); + setMetatable(L, -1, "NetworkMessage"); + return 1; +} + +int LuaScriptInterface::luaNetworkMessageDelete(lua_State* L) +{ + NetworkMessage** messagePtr = getRawUserdata(L, 1); + if (messagePtr && *messagePtr) { + delete *messagePtr; + *messagePtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaNetworkMessageGetByte(lua_State* L) +{ + // networkMessage:getByte() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->getByte()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU16(lua_State* L) +{ + // networkMessage:getU16() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU32(lua_State* L) +{ + // networkMessage:getU32() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetU64(lua_State* L) +{ + // networkMessage:getU64() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + lua_pushnumber(L, message->get()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetString(lua_State* L) +{ + // networkMessage:getString() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + pushString(L, message->getString()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageGetPosition(lua_State* L) +{ + // networkMessage:getPosition() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + pushPosition(L, message->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddByte(lua_State* L) +{ + // networkMessage:addByte(number) + uint8_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addByte(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU16(lua_State* L) +{ + // networkMessage:addU16(number) + uint16_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU32(lua_State* L) +{ + // networkMessage:addU32(number) + uint32_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddU64(lua_State* L) +{ + // networkMessage:addU64(number) + uint64_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->add(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddString(lua_State* L) +{ + // networkMessage:addString(string) + const std::string& string = getString(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addString(string); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddPosition(lua_State* L) +{ + // networkMessage:addPosition(position) + const Position& position = getPosition(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addPosition(position); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddDouble(lua_State* L) +{ + // networkMessage:addDouble(number) + double number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addDouble(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddItem(lua_State* L) +{ + // networkMessage:addItem(item) + Item* item = getUserdata(L, 2); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->addItem(item); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageAddItemId(lua_State* L) +{ + // networkMessage:addItemId(itemId) + NetworkMessage* message = getUserdata(L, 1); + if (!message) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + message->addItemId(itemId); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaNetworkMessageReset(lua_State* L) +{ + // networkMessage:reset() + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->reset(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageSkipBytes(lua_State* L) +{ + // networkMessage:skipBytes(number) + int16_t number = getNumber(L, 2); + NetworkMessage* message = getUserdata(L, 1); + if (message) { + message->skipBytes(number); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNetworkMessageSendToPlayer(lua_State* L) +{ + // networkMessage:sendToPlayer(player) + NetworkMessage* message = getUserdata(L, 1); + if (!message) { + lua_pushnil(L); + return 1; + } + + Player* player = getPlayer(L, 2); + if (player) { + player->sendNetworkMessage(*message); + pushBoolean(L, true); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + lua_pushnil(L); + } + return 1; +} + +// Item +int LuaScriptInterface::luaItemCreate(lua_State* L) +{ + // Item(uid) + uint32_t id = getNumber(L, 2); + + Item* item = getScriptEnv()->getItemByUID(id); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemIsItem(lua_State* L) +{ + // item:isItem() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaItemGetParent(lua_State* L) +{ + // item:getParent() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Cylinder* parent = item->getParent(); + if (!parent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, parent); + return 1; +} + +int LuaScriptInterface::luaItemGetTopParent(lua_State* L) +{ + // item:getTopParent() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Cylinder* topParent = item->getTopParent(); + if (!topParent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, topParent); + return 1; +} + +int LuaScriptInterface::luaItemGetId(lua_State* L) +{ + // item:getId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemClone(lua_State* L) +{ + // item:clone() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Item* clone = item->clone(); + if (!clone) { + lua_pushnil(L); + return 1; + } + + getScriptEnv()->addTempItem(clone); + clone->setParent(VirtualCylinder::virtualCylinder); + + pushUserdata(L, clone); + setItemMetatable(L, -1, clone); + return 1; +} + +int LuaScriptInterface::luaItemSplit(lua_State* L) +{ + // item:split([count = 1]) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item* item = *itemPtr; + if (!item || !item->isStackable()) { + lua_pushnil(L); + return 1; + } + + uint16_t count = std::min(getNumber(L, 2, 1), item->getItemCount()); + uint16_t diff = item->getItemCount() - count; + + Item* splitItem = item->clone(); + if (!splitItem) { + lua_pushnil(L); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + uint32_t uid = env->addThing(item); + + Item* newItem = g_game.transformItem(item, item->getID(), diff); + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertItem(uid, newItem); + } + + *itemPtr = newItem; + + splitItem->setParent(VirtualCylinder::virtualCylinder); + env->addTempItem(splitItem); + + pushUserdata(L, splitItem); + setItemMetatable(L, -1, splitItem); + return 1; +} + +int LuaScriptInterface::luaItemRemove(lua_State* L) +{ + // item:remove([count = -1]) + Item* item = getUserdata(L, 1); + if (item) { + int32_t count = getNumber(L, 2, -1); + pushBoolean(L, g_game.internalRemoveItem(item, count) == RETURNVALUE_NOERROR); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetMovementId(lua_State* L) +{ + // item:getMovementId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getMovementId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetActionId(lua_State* L) +{ + // item:getActionId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getActionId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetActionId(lua_State* L) +{ + // item:setActionId(actionId) + uint16_t actionId = getNumber(L, 2); + Item* item = getUserdata(L, 1); + if (item) { + item->setActionId(actionId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetMovementId(lua_State* L) +{ + // item:setMovementId(movementId) + uint16_t movementId = getNumber(L, 2); + Item* item = getUserdata(L, 1); + if (item) { + item->setMovementID(movementId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetUniqueId(lua_State * L) +{ + // item:getUniqueId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, getScriptEnv()->addThing(item)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetCount(lua_State* L) +{ + // item:getCount() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getItemCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetCharges(lua_State* L) +{ + // item:getCharges() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getCharges()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetFluidType(lua_State* L) +{ + // item:getFluidType() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getFluidType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetWeight(lua_State* L) +{ + // item:getWeight() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getWeight()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetSubType(lua_State* L) +{ + // item:getSubType() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, item->getSubType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetName(lua_State* L) +{ + // item:getName() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetPluralName(lua_State* L) +{ + // item:getPluralName() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getPluralName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetArticle(lua_State* L) +{ + // item:getArticle() + Item* item = getUserdata(L, 1); + if (item) { + pushString(L, item->getArticle()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetPosition(lua_State* L) +{ + // item:getPosition() + Item* item = getUserdata(L, 1); + if (item) { + pushPosition(L, item->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetTile(lua_State* L) +{ + // item:getTile() + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + Tile* tile = item->getTile(); + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemHasAttribute(lua_State* L) +{ + // item:hasAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + pushBoolean(L, item->hasAttribute(attribute)); + return 1; +} + +int LuaScriptInterface::luaItemGetAttribute(lua_State* L) +{ + // item:getAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + if (ItemAttributes::isIntAttrType(attribute)) { + lua_pushnumber(L, item->getIntAttr(attribute)); + } else if (ItemAttributes::isStrAttrType(attribute)) { + pushString(L, item->getStrAttr(attribute)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetAttribute(lua_State* L) +{ + // item:setAttribute(key, value) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + if (ItemAttributes::isIntAttrType(attribute)) { + item->setIntAttr(attribute, getNumber(L, 3)); + pushBoolean(L, true); + } else if (ItemAttributes::isStrAttrType(attribute)) { + item->setStrAttr(attribute, getString(L, 3)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemRemoveAttribute(lua_State* L) +{ + // item:removeAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + itemAttrTypes attribute; + if (isNumber(L, 2)) { + attribute = getNumber(L, 2); + } else if (isString(L, 2)) { + attribute = stringToItemAttribute(getString(L, 2)); + } else { + attribute = ITEM_ATTRIBUTE_NONE; + } + + item->removeAttribute(attribute); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaItemMoveTo(lua_State* L) +{ + // item:moveTo(position or cylinder) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item* item = *itemPtr; + if (!item || item->isRemoved()) { + lua_pushnil(L); + return 1; + } + + Cylinder* toCylinder; + if (isUserdata(L, 2)) { + const LuaDataType type = getUserdataType(L, 2); + switch (type) { + case LuaData_Container: + toCylinder = getUserdata(L, 2); + break; + case LuaData_Player: + toCylinder = getUserdata(L, 2); + break; + case LuaData_Tile: + toCylinder = getUserdata(L, 2); + break; + default: + toCylinder = nullptr; + break; + } + } else { + toCylinder = g_game.map.getTile(getPosition(L, 2)); + } + + if (!toCylinder) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() == toCylinder) { + pushBoolean(L, true); + return 1; + } + + if (item->getParent() == VirtualCylinder::virtualCylinder) { + pushBoolean(L, g_game.internalAddItem(toCylinder, item) == RETURNVALUE_NOERROR); + } else { + Item* moveItem = nullptr; + ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + if (moveItem) { + *itemPtr = moveItem; + } + pushBoolean(L, ret == RETURNVALUE_NOERROR); + } + return 1; +} + +int LuaScriptInterface::luaItemTransform(lua_State* L) +{ + // item:transform(itemId[, count/subType = -1]) + Item** itemPtr = getRawUserdata(L, 1); + if (!itemPtr) { + lua_pushnil(L); + return 1; + } + + Item*& item = *itemPtr; + if (!item) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + if (item->getID() == itemId && (subType == -1 || subType == item->getSubType())) { + pushBoolean(L, true); + return 1; + } + + const ItemType& it = Item::items[itemId]; + if (it.stackable) { + subType = std::min(subType, 100); + } + + ScriptEnvironment* env = getScriptEnv(); + uint32_t uid = env->addThing(item); + + Item* newItem = g_game.transformItem(item, itemId, subType); + if (item->isRemoved()) { + env->removeItemByUID(uid); + } + + if (newItem && newItem != item) { + env->insertItem(uid, newItem); + } + + item = newItem; + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaItemDecay(lua_State* L) +{ + // item:decay() + Item* item = getUserdata(L, 1); + if (item) { + g_game.startDecay(item); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetDescription(lua_State* L) +{ + // item:getDescription(distance) + Item* item = getUserdata(L, 1); + if (item) { + int32_t distance = getNumber(L, 2); + pushString(L, item->getDescription(distance)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemHasProperty(lua_State* L) +{ + // item:hasProperty(property) + Item* item = getUserdata(L, 1); + if (item) { + ITEMPROPERTY property = getNumber(L, 2); + pushBoolean(L, item->hasProperty(property)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Container +int LuaScriptInterface::luaContainerCreate(lua_State* L) +{ + // Container(uid) + uint32_t id = getNumber(L, 2); + + Container* container = getScriptEnv()->getContainerByUID(id); + if (container) { + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetSize(lua_State* L) +{ + // container:getSize() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetCapacity(lua_State* L) +{ + // container:getCapacity() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->capacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetEmptySlots(lua_State* L) +{ + // container:getEmptySlots([recursive = false]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint32_t slots = container->capacity() - container->size(); + bool recursive = getBoolean(L, 2, false); + if (recursive) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if (Container* tmpContainer = (*it)->getContainer()) { + slots += tmpContainer->capacity() - tmpContainer->size(); + } + } + } + lua_pushnumber(L, slots); + return 1; +} + +int LuaScriptInterface::luaContainerGetItemHoldingCount(lua_State* L) +{ + // container:getItemHoldingCount() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->getItemHoldingCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerGetItem(lua_State* L) +{ + // container:getItem(index) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint32_t index = getNumber(L, 2); + Item* item = container->getItemByIndex(index); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerHasItem(lua_State* L) +{ + // container:hasItem(item) + Item* item = getUserdata(L, 2); + Container* container = getUserdata(L, 1); + if (container) { + pushBoolean(L, container->isHoldingItem(item)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerAddItem(lua_State* L) +{ + // container:addItem(itemId[, count/subType = 1[, index = INDEX_WHEREEVER[, flags = 0]]]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + uint32_t subType = getNumber(L, 3, 1); + + Item* item = Item::CreateItem(itemId, std::min(subType, 100)); + if (!item) { + lua_pushnil(L); + return 1; + } + + int32_t index = getNumber(L, 4, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 5, 0); + + ReturnValue ret = g_game.internalAddItem(container, item, index, flags); + if (ret == RETURNVALUE_NOERROR) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + delete item; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaContainerAddItemEx(lua_State* L) +{ + // container:addItemEx(item[, index = INDEX_WHEREEVER[, flags = 0]]) + Item* item = getUserdata(L, 2); + if (!item) { + lua_pushnil(L); + return 1; + } + + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushnil(L); + return 1; + } + + int32_t index = getNumber(L, 3, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 4, 0); + ReturnValue ret = g_game.internalAddItem(container, item, index, flags); + if (ret == RETURNVALUE_NOERROR) { + ScriptEnvironment::removeTempItem(item); + } + lua_pushnumber(L, ret); + return 1; +} + +int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) +{ + // container:getItemCountById(itemId[, subType = -1]) + Container* container = getUserdata(L, 1); + if (!container) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + lua_pushnumber(L, container->getItemTypeCount(itemId, subType)); + return 1; +} + +// Teleport +int LuaScriptInterface::luaTeleportCreate(lua_State* L) +{ + // Teleport(uid) + uint32_t id = getNumber(L, 2); + + Item* item = getScriptEnv()->getItemByUID(id); + if (item && item->getTeleport()) { + pushUserdata(L, item); + setMetatable(L, -1, "Teleport"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTeleportGetDestination(lua_State* L) +{ + // teleport:getDestination() + Teleport* teleport = getUserdata(L, 1); + if (teleport) { + pushPosition(L, teleport->getDestPos()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTeleportSetDestination(lua_State* L) +{ + // teleport:setDestination(position) + Teleport* teleport = getUserdata(L, 1); + if (teleport) { + teleport->setDestPos(getPosition(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +// Creature +int LuaScriptInterface::luaCreatureCreate(lua_State* L) +{ + // Creature(id or name or userdata) + Creature* creature; + if (isNumber(L, 2)) { + creature = g_game.getCreatureByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + creature = g_game.getCreatureByName(getString(L, 2)); + } else if (isUserdata(L, 2)) { + LuaDataType type = getUserdataType(L, 2); + if (type != LuaData_Player && type != LuaData_Monster && type != LuaData_Npc) { + lua_pushnil(L); + return 1; + } + creature = getUserdata(L, 2); + } else { + creature = nullptr; + } + + if (creature) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetEvents(lua_State* L) +{ + // creature:getEvents(type) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + CreatureEventType_t eventType = getNumber(L, 2); + const auto& eventList = creature->getCreatureEvents(eventType); + lua_createtable(L, eventList.size(), 0); + + int index = 0; + for (CreatureEvent* event : eventList) { + pushString(L, event->getName()); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRegisterEvent(lua_State* L) +{ + // creature:registerEvent(name) + Creature* creature = getUserdata(L, 1); + if (creature) { + const std::string& name = getString(L, 2); + pushBoolean(L, creature->registerCreatureEvent(name)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureUnregisterEvent(lua_State* L) +{ + // creature:unregisterEvent(name) + const std::string& name = getString(L, 2); + Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->unregisterCreatureEvent(name)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureIsRemoved(lua_State* L) +{ + // creature:isRemoved() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->isRemoved()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureIsCreature(lua_State* L) +{ + // creature:isCreature() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaCreatureIsInGhostMode(lua_State* L) +{ + // creature:isInGhostMode() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->isInGhostMode()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureCanSee(lua_State* L) +{ + // creature:canSee(position) + const Creature* creature = getUserdata(L, 1); + if (creature) { + const Position& position = getPosition(L, 2); + pushBoolean(L, creature->canSee(position)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureCanSeeCreature(lua_State* L) +{ + // creature:canSeeCreature(creature) + const Creature* creature = getUserdata(L, 1); + if (creature) { + const Creature* otherCreature = getCreature(L, 2); + pushBoolean(L, creature->canSeeCreature(otherCreature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetParent(lua_State* L) +{ + // creature:getParent() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Cylinder* parent = creature->getParent(); + if (!parent) { + lua_pushnil(L); + return 1; + } + + pushCylinder(L, parent); + return 1; +} + +int LuaScriptInterface::luaCreatureGetId(lua_State* L) +{ + // creature:getId() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetName(lua_State* L) +{ + // creature:getName() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushString(L, creature->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetTarget(lua_State* L) +{ + // creature:getTarget() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* target = creature->getAttackedCreature(); + if (target) { + pushUserdata(L, target); + setCreatureMetatable(L, -1, target); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetTarget(lua_State* L) +{ + // creature:setTarget(target) + Creature* creature = getUserdata(L, 1); + if (creature) { + Creature* target = getCreature(L, 2); + pushBoolean(L, creature->setAttackedCreature(target)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetFollowCreature(lua_State* L) +{ + // creature:getFollowCreature() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* followCreature = creature->getFollowCreature(); + if (followCreature) { + pushUserdata(L, followCreature); + setCreatureMetatable(L, -1, followCreature); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetFollowCreature(lua_State* L) +{ + // creature:setFollowCreature(followedCreature) + Creature* creature = getUserdata(L, 1); + if (creature) { + Creature* followCreature = getCreature(L, 2); + pushBoolean(L, creature->setFollowCreature(followCreature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetMaster(lua_State* L) +{ + // creature:getMaster() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* master = creature->getMaster(); + if (!master) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, master); + setCreatureMetatable(L, -1, master); + return 1; +} + +int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) +{ + // creature:setMaster(master) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Creature* master = getCreature(L, 2); + if (master) { + pushBoolean(L, creature->convinceCreature(master)); + } else { + master = creature->getMaster(); + if (master) { + master->removeSummon(creature); + creature->incrementReferenceCounter(); + creature->setDropLoot(true); + } + pushBoolean(L, true); + } + + return 1; +} + +int LuaScriptInterface::luaCreatureGetLight(lua_State* L) +{ + // creature:getLight() + const Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + LightInfo light; + creature->getCreatureLight(light); + lua_pushnumber(L, light.level); + lua_pushnumber(L, light.color); + return 2; +} + +int LuaScriptInterface::luaCreatureSetLight(lua_State* L) +{ + // creature:setLight(color, level) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + LightInfo light; + light.color = getNumber(L, 2); + light.level = getNumber(L, 3); + creature->setCreatureLight(light); + g_game.changeLight(creature); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureGetSpeed(lua_State* L) +{ + // creature:getSpeed() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetBaseSpeed(lua_State* L) +{ + // creature:getBaseSpeed() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getBaseSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureChangeSpeed(lua_State* L) +{ + // creature:changeSpeed(delta) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + int32_t delta = getNumber(L, 2); + g_game.changeSpeed(creature, delta); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureSetDropLoot(lua_State* L) +{ + // creature:setDropLoot(doDrop) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setDropLoot(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPosition(lua_State* L) +{ + // creature:getPosition() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushPosition(L, creature->getPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetTile(lua_State* L) +{ + // creature:getTile() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + Tile* tile = creature->getTile(); + if (tile) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDirection(lua_State* L) +{ + // creature:getDirection() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getDirection()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetDirection(lua_State* L) +{ + // creature:setDirection(direction) + Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, g_game.internalCreatureTurn(creature, getNumber(L, 2))); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetHealth(lua_State* L) +{ + // creature:getHealth() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getHealth()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) +{ + // creature:addHealth(healthChange) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + CombatDamage damage; + damage.value = getNumber(L, 2); + if (damage.value >= 0) { + damage.type = COMBAT_HEALING; + } else { + damage.type = COMBAT_UNDEFINEDDAMAGE; + } + pushBoolean(L, g_game.combatChangeHealth(nullptr, creature, damage)); + return 1; +} + +int LuaScriptInterface::luaCreatureGetMaxHealth(lua_State* L) +{ + // creature:getMaxHealth() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getMaxHealth()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetMaxHealth(lua_State* L) +{ + // creature:setMaxHealth(maxHealth) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + creature->healthMax = getNumber(L, 2); + creature->health = std::min(creature->health, creature->healthMax); + g_game.addCreatureHealth(creature); + + Player* player = creature->getPlayer(); + if (player) { + player->sendStats(); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureGetMana(lua_State* L) +{ + // creature:getMana() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getMana()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureAddMana(lua_State* L) +{ + // creature:addMana(manaChange[, animationOnLoss = false]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + int32_t manaChange = getNumber(L, 2); + bool animationOnLoss = getBoolean(L, 3, false); + if (!animationOnLoss && manaChange < 0) { + creature->changeMana(manaChange); + } else { + g_game.combatChangeMana(nullptr, creature, manaChange); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureGetMaxMana(lua_State* L) +{ + // creature:getMaxMana() + const Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getMaxMana()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetSkull(lua_State* L) +{ + // creature:getSkull() + Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getSkull()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetSkull(lua_State* L) +{ + // creature:setSkull(skull) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setSkull(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetOutfit(lua_State* L) +{ + // creature:getOutfit() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushOutfit(L, creature->getCurrentOutfit()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureSetOutfit(lua_State* L) +{ + // creature:setOutfit(outfit) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->defaultOutfit = getOutfit(L, 2); + g_game.internalCreatureChangeOutfit(creature, creature->defaultOutfit); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetCondition(lua_State* L) +{ + // creature:getCondition(conditionType[, conditionId = CONDITIONID_COMBAT[, subId = 0]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + uint32_t subId = getNumber(L, 4, 0); + + Condition* condition = creature->getCondition(conditionType, conditionId, subId); + if (condition) { + pushUserdata(L, condition); + setWeakMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureAddCondition(lua_State* L) +{ + // creature:addCondition(condition[, force = false]) + Creature* creature = getUserdata(L, 1); + Condition* condition = getUserdata(L, 2); + if (creature && condition) { + bool force = getBoolean(L, 3, false); + pushBoolean(L, creature->addCondition(condition->clone(), force)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) +{ + // creature:removeCondition(conditionType[, conditionId = CONDITIONID_COMBAT[, subId = 0[, force = false]]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + uint32_t subId = getNumber(L, 4, 0); + Condition* condition = creature->getCondition(conditionType, conditionId, subId); + if (condition) { + bool force = getBoolean(L, 5, true); + creature->removeCondition(condition, force); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureRemove(lua_State* L) +{ + // creature:remove() + Creature** creaturePtr = getRawUserdata(L, 1); + if (!creaturePtr) { + lua_pushnil(L); + return 1; + } + + Creature* creature = *creaturePtr; + if (!creature) { + lua_pushnil(L); + return 1; + } + + Player* player = creature->getPlayer(); + if (player) { + player->kickPlayer(true); + } else { + g_game.removeCreature(creature); + } + + *creaturePtr = nullptr; + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) +{ + // creature:teleportTo(position[, pushMovement = false]) + bool pushMovement = getBoolean(L, 3, false); + + const Position& position = getPosition(L, 2); + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Position oldPosition = creature->getPosition(); + if (g_game.internalTeleport(creature, position, pushMovement) != RETURNVALUE_NOERROR) { + pushBoolean(L, false); + return 1; + } + + if (!pushMovement) { + if (oldPosition.x == position.x) { + if (oldPosition.y < position.y) { + g_game.internalCreatureTurn(creature, DIRECTION_SOUTH); + } else { + g_game.internalCreatureTurn(creature, DIRECTION_NORTH); + } + } else if (oldPosition.x > position.x) { + g_game.internalCreatureTurn(creature, DIRECTION_WEST); + } else if (oldPosition.x < position.x) { + g_game.internalCreatureTurn(creature, DIRECTION_EAST); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCreatureSay(lua_State* L) +{ + // creature:say(text, type[, ghost = false[, target = nullptr[, position]]]) + int parameters = lua_gettop(L); + + Position position; + if (parameters >= 6) { + position = getPosition(L, 6); + if (!position.x || !position.y) { + reportErrorFunc("Invalid position specified."); + pushBoolean(L, false); + return 1; + } + } + + Creature* target = nullptr; + if (parameters >= 5) { + target = getCreature(L, 5); + } + + bool ghost = getBoolean(L, 4, false); + + SpeakClasses type = getNumber(L, 3); + const std::string& text = getString(L, 2); + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + SpectatorVec list; + if (target) { + list.insert(target); + } + + if (position.x != 0) { + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list, &position)); + } else { + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list)); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDamageMap(lua_State* L) +{ + // creature:getDamageMap() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creature->damageMap.size(), 0); + for (auto damageEntry : creature->damageMap) { + lua_createtable(L, 0, 2); + setField(L, "total", damageEntry.second.total); + setField(L, "ticks", damageEntry.second.ticks); + lua_rawseti(L, -2, damageEntry.first); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetSummons(lua_State* L) +{ + // creature:getSummons() + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, creature->getSummonCount(), 0); + + int index = 0; + for (Creature* summon : creature->getSummons()) { + pushUserdata(L, summon); + setCreatureMetatable(L, -1, summon); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetDescription(lua_State* L) +{ + // creature:getDescription(distance) + int32_t distance = getNumber(L, 2); + Creature* creature = getUserdata(L, 1); + if (creature) { + pushString(L, creature->getDescription(distance)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) +{ + // creature:getPathTo(pos[, minTargetDist = 0[, maxTargetDist = 1[, fullPathSearch = true[, clearSight = true[, maxSearchDist = 0]]]]]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + const Position& position = getPosition(L, 2); + + FindPathParams fpp; + fpp.minTargetDist = getNumber(L, 3, 0); + fpp.maxTargetDist = getNumber(L, 4, 1); + fpp.fullPathSearch = getBoolean(L, 5, fpp.fullPathSearch); + fpp.clearSight = getBoolean(L, 6, fpp.clearSight); + fpp.maxSearchDist = getNumber(L, 7, fpp.maxSearchDist); + + std::forward_list dirList; + if (creature->getPathTo(position, dirList, fpp)) { + lua_newtable(L); + + int index = 0; + for (Direction dir : dirList) { + lua_pushnumber(L, dir); + lua_rawseti(L, -2, ++index); + } + } else { + pushBoolean(L, false); + } + return 1; +} + +// Player +int LuaScriptInterface::luaPlayerCreate(lua_State* L) +{ + // Player(id or name or userdata) + Player* player; + if (isNumber(L, 2)) { + player = g_game.getPlayerByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + ReturnValue ret = g_game.getPlayerByNameWildcard(getString(L, 2), player); + if (ret != RETURNVALUE_NOERROR) { + lua_pushnil(L); + lua_pushnumber(L, ret); + return 2; + } + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Player) { + lua_pushnil(L); + return 1; + } + player = getUserdata(L, 2); + } else { + player = nullptr; + } + + if (player) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsPlayer(lua_State* L) +{ + // player:isPlayer() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuid(lua_State* L) +{ + // player:getGuid() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getGUID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetIp(lua_State* L) +{ + // player:getIp() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getIP()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetAccountId(lua_State* L) +{ + // player:getAccountId() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getAccount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLastLoginSaved(lua_State* L) +{ + // player:getLastLoginSaved() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLastLoginSaved()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLastLogout(lua_State* L) +{ + // player:getLastLogout() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLastLogout()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasFlag(lua_State * L) +{ + // player:hasFlag(flag) + Player* player = getUserdata(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetAccountType(lua_State* L) +{ + // player:getAccountType() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getAccountType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetAccountType(lua_State* L) +{ + // player:setAccountType(accountType) + Player* player = getUserdata(L, 1); + if (player) { + player->accountType = getNumber(L, 2); + IOLoginData::setAccountType(player->getAccount(), player->accountType); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetCapacity(lua_State* L) +{ + // player:getCapacity() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getCapacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetCapacity(lua_State* L) +{ + // player:setCapacity(capacity) + Player* player = getUserdata(L, 1); + if (player) { + player->capacity = getNumber(L, 2); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetFreeCapacity(lua_State* L) +{ + // player:getFreeCapacity() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getFreeCapacity()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L) +{ + // player:getDepotChest(depotId[, autoCreate = false]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t depotId = getNumber(L, 2); + bool autoCreate = getBoolean(L, 3, false); + DepotLocker* depotLocker = player->getDepotLocker(depotId, autoCreate); + if (depotLocker) { + if (!depotLocker->getParent() && player->getTile()) { + depotLocker->setParent(player->getTile()); + } + + pushUserdata(L, depotLocker); + setItemMetatable(L, -1, depotLocker); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L) +{ + // player:getMurderTimestamps() + Player* player = getUserdata(L, 1); + if (player) { + lua_createtable(L, player->murderTimeStamps.size(), 0); + + uint32_t i = 1; + for (time_t currentMurderTimestamp : player->murderTimeStamps) { + lua_pushnumber(L, static_cast(currentMurderTimestamp)); + lua_rawseti(L, -2, ++i); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetPlayerKillerEnd(lua_State* L) +{ + // player:getPlayerKillerEnd() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getPlayerKillerEnd()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetPlayerKillerEnd(lua_State* L) +{ + // player:setPlayerKillerEnd(skullTime) + Player* player = getUserdata(L, 1); + if (player) { + player->setPlayerKillerEnd(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetDeathPenalty(lua_State* L) +{ + // player:getDeathPenalty() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, static_cast(player->getLostPercent() * 100)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetExperience(lua_State* L) +{ + // player:getExperience() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getExperience()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) +{ + // player:addExperience(experience[, sendText = false[, applyStages = true]) + Player* player = getUserdata(L, 1); + if (player) { + int64_t experience = getNumber(L, 2); + bool sendText = getBoolean(L, 3, false); + bool applyStages = getBoolean(L, 4, true); + player->addExperience(experience, sendText, applyStages); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveExperience(lua_State* L) +{ + // player:removeExperience(experience) + Player* player = getUserdata(L, 1); + if (player) { + int64_t experience = getNumber(L, 2); + player->removeExperience(experience); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetLevel(lua_State* L) +{ + // player:getLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMagicLevel(lua_State* L) +{ + // player:getMagicLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMagicLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMagicLevel(lua_State* L) +{ + // player:getBaseMagicLevel() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getBaseMagicLevel()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetMaxMana(lua_State* L) +{ + // player:setMaxMana(maxMana) + Player* player = getPlayer(L, 1); + if (player) { + player->manaMax = getNumber(L, 2); + player->mana = std::min(player->mana, player->manaMax); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetManaSpent(lua_State* L) +{ + // player:getManaSpent() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSpentMana()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddManaSpent(lua_State* L) +{ + // player:addManaSpent(amount) + Player* player = getUserdata(L, 1); + if (player) { + player->addManaSpent(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMaxHealth(lua_State* L) +{ + // player:getBaseMaxHealth() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->healthMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBaseMaxMana(lua_State* L) +{ + // player:getBaseMaxMana() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->manaMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillLevel(lua_State* L) +{ + // player:getSkillLevel(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetEffectiveSkillLevel(lua_State* L) +{ + // player:getEffectiveSkillLevel(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->getSkillLevel(skillType)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillPercent(lua_State* L) +{ + // player:getSkillPercent(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].percent); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkillTries(lua_State* L) +{ + // player:getSkillTries(skillType) + skills_t skillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && skillType <= SKILL_LAST) { + lua_pushnumber(L, player->skills[skillType].tries); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddSkillTries(lua_State* L) +{ + // player:addSkillTries(skillType, tries) + Player* player = getUserdata(L, 1); + if (player) { + skills_t skillType = getNumber(L, 2); + uint64_t tries = getNumber(L, 3); + player->addSkillAdvance(skillType, tries); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetItemCount(lua_State* L) +{ + // player:getItemCount(itemId[, subType = -1]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t subType = getNumber(L, 3, -1); + lua_pushnumber(L, player->getItemTypeCount(itemId, subType)); + return 1; +} + +int LuaScriptInterface::luaPlayerGetItemById(lua_State* L) +{ + // player:getItemById(itemId, deepSearch[, subType = -1]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + bool deepSearch = getBoolean(L, 3); + int32_t subType = getNumber(L, 4, -1); + + Item* item = g_game.findItemOfType(player, itemId, deepSearch, subType); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetVocation(lua_State* L) +{ + // player:getVocation() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getVocation()); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetVocation(lua_State* L) +{ + // player:setVocation(id or name or userdata) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Vocation* vocation; + if (isNumber(L, 2)) { + vocation = g_vocations.getVocation(getNumber(L, 2)); + } else if (isString(L, 2)) { + vocation = g_vocations.getVocation(g_vocations.getVocationId(getString(L, 2))); + } else if (isUserdata(L, 2)) { + vocation = getUserdata(L, 2); + } else { + vocation = nullptr; + } + + if (!vocation) { + pushBoolean(L, false); + return 1; + } + + player->setVocation(vocation->getId()); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetSex(lua_State* L) +{ + // player:getSex() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSex()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetSex(lua_State* L) +{ + // player:setSex(newSex) + Player* player = getUserdata(L, 1); + if (player) { + PlayerSex_t newSex = getNumber(L, 2); + player->setSex(newSex); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetTown(lua_State* L) +{ + // player:getTown() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getTown()); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetTown(lua_State* L) +{ + // player:setTown(town) + Town* town = getUserdata(L, 2); + if (!town) { + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (player) { + player->setTown(town); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuild(lua_State* L) +{ + // player:getGuild() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Guild* guild = player->getGuild(); + if (!guild) { + lua_pushnil(L); + return 1; + } + + pushUserdata(L, guild); + setMetatable(L, -1, "Guild"); + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuild(lua_State* L) +{ + // player:setGuild(guild) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + player->setGuild(getUserdata(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuildLevel(lua_State* L) +{ + // player:getGuildLevel() + Player* player = getUserdata(L, 1); + if (player && player->getGuild()) { + lua_pushnumber(L, player->getGuildRank()->level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuildLevel(lua_State* L) +{ + // player:setGuildLevel(level) + uint8_t level = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (!player || !player->getGuild()) { + lua_pushnil(L); + return 1; + } + + const GuildRank* rank = player->getGuild()->getRankByLevel(level); + if (!rank) { + pushBoolean(L, false); + } else { + player->setGuildRank(rank); + pushBoolean(L, true); + } + + return 1; +} + +int LuaScriptInterface::luaPlayerGetGuildNick(lua_State* L) +{ + // player:getGuildNick() + Player* player = getUserdata(L, 1); + if (player) { + pushString(L, player->getGuildNick()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGuildNick(lua_State* L) +{ + // player:setGuildNick(nick) + const std::string& nick = getString(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->setGuildNick(nick); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetGroup(lua_State* L) +{ + // player:getGroup() + Player* player = getUserdata(L, 1); + if (player) { + pushUserdata(L, player->getGroup()); + setMetatable(L, -1, "Group"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGroup(lua_State* L) +{ + // player:setGroup(group) + Group* group = getUserdata(L, 2); + if (!group) { + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (player) { + player->setGroup(group); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSoul(lua_State* L) +{ + // player:getSoul() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getSoul()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddSoul(lua_State* L) +{ + // player:addSoul(soulChange) + int32_t soulChange = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->changeSoul(soulChange); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetMaxSoul(lua_State* L) +{ + // player:getMaxSoul() + Player* player = getUserdata(L, 1); + if (player && player->vocation) { + lua_pushnumber(L, player->vocation->getSoulMax()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetBankBalance(lua_State* L) +{ + // player:getBankBalance() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getBankBalance()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetBankBalance(lua_State* L) +{ + // player:setBankBalance(bankBalance) + Player* player = getUserdata(L, 1); + if (player) { + player->setBankBalance(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L) +{ + // player:getStorageValue(key) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t key = getNumber(L, 2); + int32_t value; + if (player->getStorageValue(key, value)) { + lua_pushnumber(L, value); + } else { + lua_pushnumber(L, 0); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetStorageValue(lua_State* L) +{ + // player:setStorageValue(key, value) + int32_t value = getNumber(L, 3); + uint32_t key = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + player->addStorageValue(key, value); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddItem(lua_State* L) +{ + // player:addItem(itemId[, count = 1[, canDropOnMap = true[, subType = 1[, slot = CONST_SLOT_WHEREEVER]]]]) + Player* player = getUserdata(L, 1); + if (!player) { + pushBoolean(L, false); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + int32_t count = getNumber(L, 3, 1); + int32_t subType = getNumber(L, 5, 1); + + const ItemType& it = Item::items[itemId]; + + int32_t itemCount = 1; + int parameters = lua_gettop(L); + if (parameters >= 4) { + itemCount = std::max(1, count); + } else if (it.hasSubType()) { + if (it.stackable) { + itemCount = std::ceil(count / 100.f); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + bool hasTable = itemCount > 1; + if (hasTable) { + lua_newtable(L); + } else if (itemCount == 0) { + lua_pushnil(L); + return 1; + } + + bool canDropOnMap = getBoolean(L, 4, true); + slots_t slot = getNumber(L, 6, CONST_SLOT_WHEREEVER); + for (int32_t i = 1; i <= itemCount; ++i) { + int32_t stackCount = subType; + if (it.stackable) { + stackCount = std::min(stackCount, 100); + subType -= stackCount; + } + + Item* item = Item::CreateItem(itemId, stackCount); + if (!item) { + if (!hasTable) { + lua_pushnil(L); + } + return 1; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, item, canDropOnMap, slot); + if (ret != RETURNVALUE_NOERROR) { + delete item; + if (!hasTable) { + lua_pushnil(L); + } + return 1; + } + + if (hasTable) { + lua_pushnumber(L, i); + pushUserdata(L, item); + setItemMetatable(L, -1, item); + lua_settable(L, -3); + } else { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddItemEx(lua_State* L) +{ + // player:addItemEx(item[, canDropOnMap = false[, index = INDEX_WHEREEVER[, flags = 0]]]) + // player:addItemEx(item[, canDropOnMap = true[, slot = CONST_SLOT_WHEREEVER]]) + Item* item = getUserdata(L, 2); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + pushBoolean(L, false); + return 1; + } + + bool canDropOnMap = getBoolean(L, 3, false); + ReturnValue returnValue; + if (canDropOnMap) { + slots_t slot = getNumber(L, 4, CONST_SLOT_WHEREEVER); + returnValue = g_game.internalPlayerAddItem(player, item, true, slot); + } else { + int32_t index = getNumber(L, 4, INDEX_WHEREEVER); + uint32_t flags = getNumber(L, 5, 0); + returnValue = g_game.internalAddItem(player, item, index, flags); + } + + if (returnValue == RETURNVALUE_NOERROR) { + ScriptEnvironment::removeTempItem(item); + } + lua_pushnumber(L, returnValue); + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveItem(lua_State* L) +{ + // player:removeItem(itemId, count[, subType = -1[, ignoreEquipped = false]]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + uint32_t count = getNumber(L, 3); + int32_t subType = getNumber(L, 4, -1); + bool ignoreEquipped = getBoolean(L, 5, false); + pushBoolean(L, player->removeItemOfType(itemId, count, subType, ignoreEquipped)); + return 1; +} + +int LuaScriptInterface::luaPlayerGetMoney(lua_State* L) +{ + // player:getMoney() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getMoney()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddMoney(lua_State* L) +{ + // player:addMoney(money) + uint64_t money = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + g_game.addMoney(player, money); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L) +{ + // player:removeMoney(money) + Player* player = getUserdata(L, 1); + if (player) { + uint64_t money = getNumber(L, 2); + pushBoolean(L, g_game.removeMoney(player, money)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) +{ + // player:showTextDialog(itemId[, text[, canWrite[, length]]]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + int32_t length = getNumber(L, 5, -1); + bool canWrite = getBoolean(L, 4, false); + std::string text; + + int parameters = lua_gettop(L); + if (parameters >= 3) { + text = getString(L, 3); + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + Item* item = Item::CreateItem(itemId); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (length < 0) { + length = Item::items[item->getID()].maxTextLen; + } + + if (!text.empty()) { + item->setText(text); + length = std::max(text.size(), length); + } + + item->setParent(player); + player->setWriteItem(item, length); + player->sendTextWindow(item, length, canWrite); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerSendTextMessage(lua_State* L) +{ + // player:sendTextMessage(type, text) + TextMessage message(getNumber(L, 2), getString(L, 3)); + Player* player = getUserdata(L, 1); + if (player) { + player->sendTextMessage(message); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSendPrivateMessage(lua_State* L) +{ + // player:sendPrivateMessage(speaker, text[, type]) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + const Player* speaker = getUserdata(L, 2); + const std::string& text = getString(L, 3); + SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE); + player->sendPrivateMessage(speaker, type, text); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerChannelSay(lua_State* L) +{ + // player:channelSay(speaker, type, text, channelId) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Creature* speaker = getCreature(L, 2); + SpeakClasses type = getNumber(L, 3); + const std::string& text = getString(L, 4); + uint16_t channelId = getNumber(L, 5); + player->sendToChannel(speaker, type, text, channelId); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerOpenChannel(lua_State* L) +{ + // player:openChannel(channelId) + uint16_t channelId = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player) { + g_game.playerOpenChannel(player->getID(), channelId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSlotItem(lua_State* L) +{ + // player:getSlotItem(slot) + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint32_t slot = getNumber(L, 2); + Thing* thing = player->getThing(slot); + if (!thing) { + lua_pushnil(L); + return 1; + } + + Item* item = thing->getItem(); + if (item) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetParty(lua_State* L) +{ + // player:getParty() + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Party* party = player->getParty(); + if (party) { + pushUserdata(L, party); + setMetatable(L, -1, "Party"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L) +{ + // player:sendOutfitWindow() + Player* player = getUserdata(L, 1); + if (player) { + player->sendOutfitWindow(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L) +{ + // player:getPremiumDays() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->premiumDays); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddPremiumDays(lua_State* L) +{ + // player:addPremiumDays(days) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (player->premiumDays != std::numeric_limits::max()) { + uint16_t days = getNumber(L, 2); + int32_t addDays = std::min(0xFFFE - player->premiumDays, days); + if (addDays > 0) { + player->setPremiumDays(player->premiumDays + addDays); + IOLoginData::addPremiumDays(player->getAccount(), addDays); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerRemovePremiumDays(lua_State* L) +{ + // player:removePremiumDays(days) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (player->premiumDays != std::numeric_limits::max()) { + uint16_t days = getNumber(L, 2); + int32_t removeDays = std::min(player->premiumDays, days); + if (removeDays > 0) { + player->setPremiumDays(player->premiumDays - removeDays); + IOLoginData::removePremiumDays(player->getAccount(), removeDays); + } + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerHasBlessing(lua_State* L) +{ + // player:hasBlessing(blessing) + uint8_t blessing = getNumber(L, 2) - 1; + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->hasBlessing(blessing)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddBlessing(lua_State* L) +{ + // player:addBlessing(blessing) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t blessing = getNumber(L, 2) - 1; + if (player->hasBlessing(blessing)) { + pushBoolean(L, false); + return 1; + } + + player->addBlessing(1 << blessing); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveBlessing(lua_State* L) +{ + // player:removeBlessing(blessing) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t blessing = getNumber(L, 2) - 1; + if (!player->hasBlessing(blessing)) { + pushBoolean(L, false); + return 1; + } + + player->removeBlessing(1 << blessing); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerCanLearnSpell(lua_State* L) +{ + // player:canLearnSpell(spellName) + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + const std::string& spellName = getString(L, 2); + InstantSpell* spell = g_spells->getInstantSpellByName(spellName); + if (!spell) { + reportErrorFunc("Spell \"" + spellName + "\" not found"); + pushBoolean(L, false); + return 1; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + pushBoolean(L, true); + return 1; + } + + const auto& vocMap = spell->getVocMap(); + if (vocMap.count(player->getVocationId()) == 0) { + pushBoolean(L, false); + } else if (player->getLevel() < spell->getLevel()) { + pushBoolean(L, false); + } else if (player->getMagicLevel() < spell->getMagicLevel()) { + pushBoolean(L, false); + } else { + pushBoolean(L, true); + } + return 1; +} + +int LuaScriptInterface::luaPlayerLearnSpell(lua_State* L) +{ + // player:learnSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + player->learnInstantSpell(spellName); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerForgetSpell(lua_State* L) +{ + // player:forgetSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + player->forgetInstantSpell(spellName); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasLearnedSpell(lua_State* L) +{ + // player:hasLearnedSpell(spellName) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& spellName = getString(L, 2); + pushBoolean(L, player->hasLearnedInstantSpell(spellName)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSave(lua_State* L) +{ + // player:save() + Player* player = getUserdata(L, 1); + if (player) { + player->loginPosition = player->getPosition(); + pushBoolean(L, IOLoginData::savePlayer(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsPzLocked(lua_State* L) +{ + // player:isPzLocked() + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->isPzLocked()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetClient(lua_State* L) +{ + // player:getClient() + Player* player = getUserdata(L, 1); + if (player) { + lua_createtable(L, 0, 2); + setField(L, "version", player->getProtocolVersion()); + setField(L, "os", player->getOperatingSystem()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetHouse(lua_State* L) +{ + // player:getHouse() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + House* house = g_game.map.houses.getHouseByPlayerId(player->getGUID()); + if (house) { + pushUserdata(L, house); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) +{ + // player:setGhostMode(enabled) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + bool enabled = getBoolean(L, 2); + if (player->isInGhostMode() == enabled) { + pushBoolean(L, true); + return 1; + } + + player->switchGhostMode(); + + Tile* tile = player->getTile(); + const Position& position = player->getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, position, true, true); + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { + if (enabled) { + tmpPlayer->sendRemoveTileThing(position, tile->getStackposOfCreature(tmpPlayer, player)); + } else { + tmpPlayer->sendCreatureAppear(player, position, true); + } + } else { + tmpPlayer->sendCreatureChangeVisible(player, !enabled); + } + } + + if (player->isInGhostMode()) { + for (const auto& it : g_game.getPlayers()) { + if (!it.second->isAccessPlayer()) { + it.second->notifyStatusChange(player, VIPSTATUS_OFFLINE); + } + } + IOLoginData::updateOnlineStatus(player->getGUID(), false); + } else { + for (const auto& it : g_game.getPlayers()) { + if (!it.second->isAccessPlayer()) { + it.second->notifyStatusChange(player, VIPSTATUS_ONLINE); + } + } + IOLoginData::updateOnlineStatus(player->getGUID(), true); + } + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerId(lua_State* L) +{ + // player:getContainerId(container) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Container* container = getUserdata(L, 2); + if (container) { + lua_pushnumber(L, player->getContainerID(container)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerById(lua_State* L) +{ + // player:getContainerById(id) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Container* container = player->getContainerByID(getNumber(L, 2)); + if (container) { + pushUserdata(L, container); + setMetatable(L, -1, "Container"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetContainerIndex(lua_State* L) +{ + // player:getContainerIndex(id) + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getContainerIndex(getNumber(L, 2))); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetTotalDamage(lua_State * L) +{ + // player:getTotalDamage(attackSkill, attackValue, fightMode) + Player* player = getUserdata(L, 1); + if (player) { + uint32_t attackSkill = getNumber(L, 2); + uint32_t attackValue = getNumber(L, 3); + fightMode_t fightMode = static_cast(getNumber(L, 4, FIGHTMODE_BALANCED)); + + lua_pushnumber(L, Combat::getTotalDamage(attackSkill, attackValue, fightMode)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Monster +int LuaScriptInterface::luaMonsterCreate(lua_State* L) +{ + // Monster(id or userdata) + Monster* monster; + if (isNumber(L, 2)) { + monster = g_game.getMonsterByID(getNumber(L, 2)); + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Monster) { + lua_pushnil(L); + return 1; + } + monster = getUserdata(L, 2); + } else { + monster = nullptr; + } + + if (monster) { + pushUserdata(L, monster); + setMetatable(L, -1, "Monster"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsMonster(lua_State* L) +{ + // monster:isMonster() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaMonsterGetType(lua_State* L) +{ + // monster:getType() + const Monster* monster = getUserdata(L, 1); + if (monster) { + pushUserdata(L, monster->mType); + setMetatable(L, -1, "MonsterType"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetSpawnPosition(lua_State* L) +{ + // monster:getSpawnPosition() + const Monster* monster = getUserdata(L, 1); + if (monster) { + pushPosition(L, monster->getMasterPos()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsInSpawnRange(lua_State* L) +{ + // monster:isInSpawnRange([position]) + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->isInSpawnRange(lua_gettop(L) >= 2 ? getPosition(L, 2) : monster->getPosition())); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsIdle(lua_State* L) +{ + // monster:isIdle() + Monster* monster = getUserdata(L, 1); + if (monster) { + pushBoolean(L, monster->getIdleStatus()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSetIdle(lua_State* L) +{ + // monster:setIdle(idle) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + monster->setIdle(getBoolean(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterIsTarget(lua_State* L) +{ + // monster:isTarget(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isTarget(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsOpponent(lua_State* L) +{ + // monster:isOpponent(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isOpponent(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterIsFriend(lua_State* L) +{ + // monster:isFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + const Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->isFriend(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterAddFriend(lua_State* L) +{ + // monster:addFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + monster->addFriend(creature); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterRemoveFriend(lua_State* L) +{ + // monster:removeFriend(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + monster->removeFriend(creature); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetFriendList(lua_State* L) +{ + // monster:getFriendList() + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const auto& friendList = monster->getFriendList(); + lua_createtable(L, friendList.size(), 0); + + int index = 0; + for (Creature* creature : friendList) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetFriendCount(lua_State* L) +{ + // monster:getFriendCount() + Monster* monster = getUserdata(L, 1); + if (monster) { + lua_pushnumber(L, monster->getFriendList().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterAddTarget(lua_State* L) +{ + // monster:addTarget(creature[, pushFront = false]) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + Creature* creature = getCreature(L, 2); + bool pushFront = getBoolean(L, 3, false); + monster->addTarget(creature, pushFront); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterRemoveTarget(lua_State* L) +{ + // monster:removeTarget(creature) + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + monster->removeTarget(getCreature(L, 2)); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaMonsterGetTargetList(lua_State* L) +{ + // monster:getTargetList() + Monster* monster = getUserdata(L, 1); + if (!monster) { + lua_pushnil(L); + return 1; + } + + const auto& targetList = monster->getTargetList(); + lua_createtable(L, targetList.size(), 0); + + int index = 0; + for (Creature* creature : targetList) { + pushUserdata(L, creature); + setCreatureMetatable(L, -1, creature); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterGetTargetCount(lua_State* L) +{ + // monster:getTargetCount() + Monster* monster = getUserdata(L, 1); + if (monster) { + lua_pushnumber(L, monster->getTargetList().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSelectTarget(lua_State* L) +{ + // monster:selectTarget(creature) + Monster* monster = getUserdata(L, 1); + if (monster) { + Creature* creature = getCreature(L, 2); + pushBoolean(L, monster->selectTarget(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSearchTarget(lua_State* L) +{ + // monster:searchTarget([searchType = TARGETSEARCH_ANY]) + Monster* monster = getUserdata(L, 1); + if (monster) { + TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_ANY); + pushBoolean(L, monster->searchTarget(searchType)); + } else { + lua_pushnil(L); + } + return 1; +} + +// Npc +int LuaScriptInterface::luaNpcCreate(lua_State* L) +{ + // Npc([id or name or userdata]) + Npc* npc; + if (lua_gettop(L) >= 2) { + if (isNumber(L, 2)) { + npc = g_game.getNpcByID(getNumber(L, 2)); + } else if (isString(L, 2)) { + npc = g_game.getNpcByName(getString(L, 2)); + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Npc) { + lua_pushnil(L); + return 1; + } + npc = getUserdata(L, 2); + } else { + npc = nullptr; + } + } else { + npc = getScriptEnv()->getNpc(); + } + + if (npc) { + pushUserdata(L, npc); + setMetatable(L, -1, "Npc"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNpcIsNpc(lua_State* L) +{ + // npc:isNpc() + pushBoolean(L, getUserdata(L, 1) != nullptr); + return 1; +} + +int LuaScriptInterface::luaNpcSetMasterPos(lua_State* L) +{ + // npc:setMasterPos(pos[, radius]) + Npc* npc = getUserdata(L, 1); + if (!npc) { + lua_pushnil(L); + return 1; + } + + const Position& pos = getPosition(L, 2); + int32_t radius = getNumber(L, 3, 1); + npc->setMasterPos(pos, radius); + pushBoolean(L, true); + return 1; +} + +// Guild +int LuaScriptInterface::luaGuildCreate(lua_State* L) +{ + // Guild(id) + uint32_t id = getNumber(L, 2); + + Guild* guild = g_game.getGuild(id); + if (guild) { + pushUserdata(L, guild); + setMetatable(L, -1, "Guild"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetId(lua_State* L) +{ + // guild:getId() + Guild* guild = getUserdata(L, 1); + if (guild) { + lua_pushnumber(L, guild->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetName(lua_State* L) +{ + // guild:getName() + Guild* guild = getUserdata(L, 1); + if (guild) { + pushString(L, guild->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetMembersOnline(lua_State* L) +{ + // guild:getMembersOnline() + const Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + const auto& members = guild->getMembersOnline(); + lua_createtable(L, members.size(), 0); + + int index = 0; + for (Player* player : members) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaGuildAddRank(lua_State* L) +{ + // guild:addRank(id, name, level) + Guild* guild = getUserdata(L, 1); + if (guild) { + uint32_t id = getNumber(L, 2); + const std::string& name = getString(L, 3); + uint8_t level = getNumber(L, 4); + guild->addRank(id, name, level); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetRankById(lua_State* L) +{ + // guild:getRankById(id) + Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + uint32_t id = getNumber(L, 2); + GuildRank* rank = guild->getRankById(id); + if (rank) { + lua_createtable(L, 0, 3); + setField(L, "id", rank->id); + setField(L, "name", rank->name); + setField(L, "level", rank->level); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildGetRankByLevel(lua_State* L) +{ + // guild:getRankByLevel(level) + const Guild* guild = getUserdata(L, 1); + if (!guild) { + lua_pushnil(L); + return 1; + } + + uint8_t level = getNumber(L, 2); + const GuildRank* rank = guild->getRankByLevel(level); + if (rank) { + lua_createtable(L, 0, 3); + setField(L, "id", rank->id); + setField(L, "name", rank->name); + setField(L, "level", rank->level); + } else { + lua_pushnil(L); + } + return 1; +} + +// Group +int LuaScriptInterface::luaGroupCreate(lua_State* L) +{ + // Group(id) + uint32_t id = getNumber(L, 2); + + Group* group = g_game.groups.getGroup(id); + if (group) { + pushUserdata(L, group); + setMetatable(L, -1, "Group"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetId(lua_State* L) +{ + // group:getId() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->id); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetName(lua_State* L) +{ + // group:getName() + Group* group = getUserdata(L, 1); + if (group) { + pushString(L, group->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetFlags(lua_State* L) +{ + // group:getFlags() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->flags); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetAccess(lua_State* L) +{ + // group:getAccess() + Group* group = getUserdata(L, 1); + if (group) { + pushBoolean(L, group->access); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetMaxDepotItems(lua_State* L) +{ + // group:getMaxDepotItems() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->maxDepotItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGroupGetMaxVipEntries(lua_State* L) +{ + // group:getMaxVipEntries() + Group* group = getUserdata(L, 1); + if (group) { + lua_pushnumber(L, group->maxVipEntries); + } else { + lua_pushnil(L); + } + return 1; +} + +// Vocation +int LuaScriptInterface::luaVocationCreate(lua_State* L) +{ + // Vocation(id or name) + uint32_t id; + if (isNumber(L, 2)) { + id = getNumber(L, 2); + } else { + id = g_vocations.getVocationId(getString(L, 2)); + } + + Vocation* vocation = g_vocations.getVocation(id); + if (vocation) { + pushUserdata(L, vocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetId(lua_State* L) +{ + // vocation:getId() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetName(lua_State* L) +{ + // vocation:getName() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + pushString(L, vocation->getVocName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetDescription(lua_State* L) +{ + // vocation:getDescription() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + pushString(L, vocation->getVocDescription()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetRequiredSkillTries(lua_State* L) +{ + // vocation:getRequiredSkillTries(skillType, skillLevel) + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + skills_t skillType = getNumber(L, 2); + uint16_t skillLevel = getNumber(L, 3); + lua_pushnumber(L, vocation->getReqSkillTries(skillType, skillLevel)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetRequiredManaSpent(lua_State* L) +{ + // vocation:getRequiredManaSpent(magicLevel) + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + uint32_t magicLevel = getNumber(L, 2); + lua_pushnumber(L, vocation->getReqMana(magicLevel)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetCapacityGain(lua_State* L) +{ + // vocation:getCapacityGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getCapGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGain(lua_State* L) +{ + // vocation:getHealthGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHPGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGainTicks(lua_State* L) +{ + // vocation:getHealthGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHealthGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetHealthGainAmount(lua_State* L) +{ + // vocation:getHealthGainAmount() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getHealthGainAmount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGain(lua_State* L) +{ + // vocation:getManaGain() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGain()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGainTicks(lua_State* L) +{ + // vocation:getManaGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetManaGainAmount(lua_State* L) +{ + // vocation:getManaGainAmount() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getManaGainAmount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetMaxSoul(lua_State* L) +{ + // vocation:getMaxSoul() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getSoulMax()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetSoulGainTicks(lua_State* L) +{ + // vocation:getSoulGainTicks() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getSoulGainTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetAttackSpeed(lua_State* L) +{ + // vocation:getAttackSpeed() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getAttackSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetBaseSpeed(lua_State* L) +{ + // vocation:getBaseSpeed() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getBaseSpeed()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetDemotion(lua_State* L) +{ + // vocation:getDemotion() + Vocation* vocation = getUserdata(L, 1); + if (!vocation) { + lua_pushnil(L); + return 1; + } + + uint16_t fromId = vocation->getFromVocation(); + if (fromId == VOCATION_NONE) { + lua_pushnil(L); + return 1; + } + + Vocation* demotedVocation = g_vocations.getVocation(fromId); + if (demotedVocation && demotedVocation != vocation) { + pushUserdata(L, demotedVocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaVocationGetPromotion(lua_State* L) +{ + // vocation:getPromotion() + Vocation* vocation = getUserdata(L, 1); + if (!vocation) { + lua_pushnil(L); + return 1; + } + + uint16_t promotedId = g_vocations.getPromotedVocation(vocation->getId()); + if (promotedId == VOCATION_NONE) { + lua_pushnil(L); + return 1; + } + + Vocation* promotedVocation = g_vocations.getVocation(promotedId); + if (promotedVocation && promotedVocation != vocation) { + pushUserdata(L, promotedVocation); + setMetatable(L, -1, "Vocation"); + } else { + lua_pushnil(L); + } + return 1; +} + +// Town +int LuaScriptInterface::luaTownCreate(lua_State* L) +{ + // Town(id or name) + Town* town; + if (isNumber(L, 2)) { + town = g_game.map.towns.getTown(getNumber(L, 2)); + } else if (isString(L, 2)) { + town = g_game.map.towns.getTown(getString(L, 2)); + } else { + town = nullptr; + } + + if (town) { + pushUserdata(L, town); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetId(lua_State* L) +{ + // town:getId() + Town* town = getUserdata(L, 1); + if (town) { + lua_pushnumber(L, town->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetName(lua_State* L) +{ + // town:getName() + Town* town = getUserdata(L, 1); + if (town) { + pushString(L, town->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTownGetTemplePosition(lua_State* L) +{ + // town:getTemplePosition() + Town* town = getUserdata(L, 1); + if (town) { + pushPosition(L, town->getTemplePosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +// House +int LuaScriptInterface::luaHouseCreate(lua_State* L) +{ + // House(id) + House* house = g_game.map.houses.getHouse(getNumber(L, 2)); + if (house) { + pushUserdata(L, house); + setMetatable(L, -1, "House"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetId(lua_State* L) +{ + // house:getId() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetName(lua_State* L) +{ + // house:getName() + House* house = getUserdata(L, 1); + if (house) { + pushString(L, house->getName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTown(lua_State* L) +{ + // house:getTown() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + Town* town = g_game.map.towns.getTown(house->getTownId()); + if (town) { + pushUserdata(L, town); + setMetatable(L, -1, "Town"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetExitPosition(lua_State* L) +{ + // house:getExitPosition() + House* house = getUserdata(L, 1); + if (house) { + pushPosition(L, house->getEntryPosition()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetRent(lua_State* L) +{ + // house:getRent() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getRent()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetOwnerGuid(lua_State* L) +{ + // house:getOwnerGuid() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getOwner()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetOwnerGuid(lua_State* L) +{ + // house:setOwnerGuid(guid[, updateDatabase = true]) + House* house = getUserdata(L, 1); + if (house) { + uint32_t guid = getNumber(L, 2); + bool updateDatabase = getBoolean(L, 3, true); + house->setOwner(guid, updateDatabase); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetBeds(lua_State* L) +{ + // house:getBeds() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& beds = house->getBeds(); + lua_createtable(L, beds.size(), 0); + + int index = 0; + for (BedItem* bedItem : beds) { + pushUserdata(L, bedItem); + setItemMetatable(L, -1, bedItem); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetBedCount(lua_State* L) +{ + // house:getBedCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getBedCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetDoors(lua_State* L) +{ + // house:getDoors() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& doors = house->getDoors(); + lua_createtable(L, doors.size(), 0); + + int index = 0; + for (Door* door : doors) { + pushUserdata(L, door); + setItemMetatable(L, -1, door); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetDoorCount(lua_State* L) +{ + // house:getDoorCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getDoors().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTiles(lua_State* L) +{ + // house:getTiles() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& tiles = house->getTiles(); + lua_createtable(L, tiles.size(), 0); + + int index = 0; + for (Tile* tile : tiles) { + pushUserdata(L, tile); + setMetatable(L, -1, "Tile"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) +{ + // house:getTileCount() + House* house = getUserdata(L, 1); + if (house) { + lua_pushnumber(L, house->getTiles().size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaHouseGetAccessList(lua_State* L) +{ + // house:getAccessList(listId) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + std::string list; + uint32_t listId = getNumber(L, 2); + if (house->getAccessList(listId, list)) { + pushString(L, list); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaHouseSetAccessList(lua_State* L) +{ + // house:setAccessList(listId, list) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + uint32_t listId = getNumber(L, 2); + const std::string& list = getString(L, 3); + house->setAccessList(listId, list); + pushBoolean(L, true); + return 1; +} + +// ItemType +int LuaScriptInterface::luaItemTypeCreate(lua_State* L) +{ + // ItemType(id or name) + uint32_t id; + if (isNumber(L, 2)) { + id = getNumber(L, 2); + } else { + id = Item::items.getItemIdByName(getString(L, 2)); + } + + const ItemType& itemType = Item::items[id]; + pushUserdata(L, &itemType); + setMetatable(L, -1, "ItemType"); + return 1; +} + +int LuaScriptInterface::luaItemTypeIsCorpse(lua_State* L) +{ + // itemType:isCorpse() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->corpse); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDoor(lua_State* L) +{ + // itemType:isDoor() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isDoor()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsContainer(lua_State* L) +{ + // itemType:isContainer() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isContainer()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsChest(lua_State * L) +{ + // itemType:isChest() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isChest()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsFluidContainer(lua_State* L) +{ + // itemType:isFluidContainer() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isFluidContainer()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsMovable(lua_State* L) +{ + // itemType:isMovable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->moveable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsRune(lua_State* L) +{ + // itemType:isRune() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isRune()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsStackable(lua_State* L) +{ + // itemType:isStackable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->stackable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsReadable(lua_State* L) +{ + // itemType:isReadable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->canReadText); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsWritable(lua_State* L) +{ + // itemType:isWritable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->canWriteText); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) +{ + // itemType:isMagicField() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isMagicField()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsSplash(lua_State* L) +{ + // itemType:isSplash() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isSplash()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsKey(lua_State* L) +{ + // itemType:isKey() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isKey()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDisguised(lua_State* L) +{ + // itemType:isDisguised() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->disguise); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDestroyable(lua_State * L) +{ + // itemType:isDestroyable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->destroy); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State * L) +{ + // itemType:isGroundTile() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->group == ITEM_GROUP_GROUND); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetType(lua_State* L) +{ + // itemType:getType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->type); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetId(lua_State* L) +{ + // itemType:getId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->id); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDisguiseId(lua_State * L) +{ + // itemType:getDisguiseId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->disguiseId); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetName(lua_State* L) +{ + // itemType:getName() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetPluralName(lua_State* L) +{ + // itemType:getPluralName() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->getPluralName()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetArticle(lua_State* L) +{ + // itemType:getArticle() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->article); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDescription(lua_State* L) +{ + // itemType:getDescription() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushString(L, itemType->description); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) +{ + // itemType:getSlotPosition() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->slotPosition); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDestroyTarget(lua_State * L) +{ + // itemType:getDestroyTarget() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->destroyTarget); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetCharges(lua_State* L) +{ + // itemType:getCharges() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->charges); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetFluidSource(lua_State* L) +{ + // itemType:getFluidSource() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->fluidSource); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetCapacity(lua_State* L) +{ + // itemType:getCapacity() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->maxItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) +{ + // itemType:getWeight([count = 1]) + uint16_t count = getNumber(L, 2, 1); + + const ItemType* itemType = getUserdata(L, 1); + if (!itemType) { + lua_pushnil(L); + return 1; + } + + uint64_t weight = static_cast(itemType->weight) * std::max(1, count); + lua_pushnumber(L, weight); + return 1; +} + +int LuaScriptInterface::luaItemTypeGetShootRange(lua_State* L) +{ + // itemType:getShootRange() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->shootRange); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetAttack(lua_State* L) +{ + // itemType:getAttack() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->attack); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) +{ + // itemType:getDefense() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->defense); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) +{ + // itemType:getArmor() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->armor); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) +{ + // itemType:getWeaponType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->weaponType); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L) +{ + // itemType:getTransformEquipId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->transformEquipTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetTransformDeEquipId(lua_State* L) +{ + // itemType:getTransformDeEquipId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->transformDeEquipTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) +{ + // itemType:getDecayId() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->decayTo); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetNutrition(lua_State* L) +{ + // itemType:getNutrition() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->nutrition); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetRequiredLevel(lua_State* L) +{ + // itemType:getRequiredLevel() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->minReqLevel); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeHasSubType(lua_State* L) +{ + // itemType:hasSubType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->hasSubType()); + } else { + lua_pushnil(L); + } + return 1; +} + +// Combat +int LuaScriptInterface::luaCombatCreate(lua_State* L) +{ + // Combat() + pushUserdata(L, g_luaEnvironment.createCombatObject(getScriptEnv()->getScriptInterface())); + setMetatable(L, -1, "Combat"); + return 1; +} + +int LuaScriptInterface::luaCombatSetParameter(lua_State* L) +{ + // combat:setParameter(key, value) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + CombatParam_t key = getNumber(L, 2); + uint32_t value; + if (isBoolean(L, 3)) { + value = getBoolean(L, 3) ? 1 : 0; + } else { + value = getNumber(L, 3); + } + combat->setParam(key, value); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCombatSetFormula(lua_State* L) +{ + // combat:setFormula(type, mina, minb, maxa, maxb) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + formulaType_t type = getNumber(L, 2); + double mina = getNumber(L, 3); + double minb = getNumber(L, 4); + double maxa = getNumber(L, 5); + double maxb = getNumber(L, 6); + combat->setPlayerCombatValues(type, mina, minb, maxa, maxb); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaCombatSetArea(lua_State* L) +{ + // combat:setArea(area) + if (getScriptEnv()->getScriptId() != EVENT_ID_LOADING) { + reportErrorFunc("This function can only be used while loading the script."); + lua_pushnil(L); + return 1; + } + + const AreaCombat* area = g_luaEnvironment.getAreaObject(getNumber(L, 2)); + if (!area) { + reportErrorFunc(getErrorDesc(LUA_ERROR_AREA_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + Combat* combat = getUserdata(L, 1); + if (combat) { + combat->setArea(new AreaCombat(*area)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatSetCondition(lua_State* L) +{ + // combat:setCondition(condition) + Condition* condition = getUserdata(L, 2); + Combat* combat = getUserdata(L, 1); + if (combat && condition) { + combat->setCondition(condition->clone()); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatSetCallback(lua_State* L) +{ + // combat:setCallback(key, function) + Combat* combat = getUserdata(L, 1); + if (!combat) { + lua_pushnil(L); + return 1; + } + + CallBackParam_t key = getNumber(L, 2); + if (!combat->setCallback(key)) { + lua_pushnil(L); + return 1; + } + + CallBack* callback = combat->getCallback(key); + if (!callback) { + lua_pushnil(L); + return 1; + } + + const std::string& function = getString(L, 3); + pushBoolean(L, callback->loadCallBack(getScriptEnv()->getScriptInterface(), function)); + return 1; +} + +int LuaScriptInterface::luaCombatExecute(lua_State* L) +{ + // combat:execute(creature, variant) + Combat* combat = getUserdata(L, 1); + if (!combat) { + pushBoolean(L, false); + return 1; + } + + Creature* creature = getCreature(L, 2); + + const LuaVariant& variant = getVariant(L, 3); + switch (variant.type) { + case VARIANT_NUMBER: { + Creature* target = g_game.getCreatureByID(variant.number); + if (!target) { + pushBoolean(L, false); + return 1; + } + + if (combat->hasArea()) { + combat->doCombat(creature, target->getPosition()); + } else { + combat->doCombat(creature, target); + } + break; + } + + case VARIANT_POSITION: { + combat->doCombat(creature, variant.pos); + break; + } + + case VARIANT_TARGETPOSITION: { + if (combat->hasArea()) { + combat->doCombat(creature, variant.pos); + } else { + combat->postCombatEffects(creature, variant.pos); + g_game.addMagicEffect(variant.pos, CONST_ME_POFF); + } + break; + } + + case VARIANT_STRING: { + Player* target = g_game.getPlayerByName(variant.text); + if (!target) { + pushBoolean(L, false); + return 1; + } + + combat->doCombat(creature, target); + break; + } + + case VARIANT_NONE: { + reportErrorFunc(getErrorDesc(LUA_ERROR_VARIANT_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + default: { + break; + } + } + + pushBoolean(L, true); + return 1; +} + +// Condition +int LuaScriptInterface::luaConditionCreate(lua_State* L) +{ + // Condition(conditionType[, conditionId = CONDITIONID_COMBAT]) + ConditionType_t conditionType = getNumber(L, 2); + ConditionId_t conditionId = getNumber(L, 3, CONDITIONID_COMBAT); + + Condition* condition = Condition::createCondition(conditionId, conditionType, 0, 0); + if (condition) { + pushUserdata(L, condition); + setMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionDelete(lua_State* L) +{ + // condition:delete() + Condition** conditionPtr = getRawUserdata(L, 1); + if (conditionPtr && *conditionPtr) { + delete *conditionPtr; + *conditionPtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaConditionGetId(lua_State* L) +{ + // condition:getId() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetSubId(lua_State* L) +{ + // condition:getSubId() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getSubId()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetType(lua_State* L) +{ + // condition:getType() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getType()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetIcons(lua_State* L) +{ + // condition:getIcons() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getIcons()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetEndTime(lua_State* L) +{ + // condition:getEndTime() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getEndTime()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionClone(lua_State* L) +{ + // condition:clone() + Condition* condition = getUserdata(L, 1); + if (condition) { + pushUserdata(L, condition->clone()); + setMetatable(L, -1, "Condition"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionGetTicks(lua_State* L) +{ + // condition:getTicks() + Condition* condition = getUserdata(L, 1); + if (condition) { + lua_pushnumber(L, condition->getTicks()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetTicks(lua_State* L) +{ + // condition:setTicks(ticks) + int32_t ticks = getNumber(L, 2); + Condition* condition = getUserdata(L, 1); + if (condition) { + condition->setTicks(ticks); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetParameter(lua_State* L) +{ + // condition:setParameter(key, value) + Condition* condition = getUserdata(L, 1); + if (!condition) { + lua_pushnil(L); + return 1; + } + + ConditionParam_t key = getNumber(L, 2); + int32_t value; + if (isBoolean(L, 3)) { + value = getBoolean(L, 3) ? 1 : 0; + } else { + value = getNumber(L, 3); + } + condition->setParam(key, value); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L) +{ + // condition:setSpeedDelta(speedDelta) + int32_t speedDelta = getNumber(L, 2); + ConditionSpeed* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + condition->setSpeedDelta(speedDelta); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) +{ + // condition:setOutfit(outfit) + // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet) + Outfit_t outfit; + if (isTable(L, 2)) { + outfit = getOutfit(L, 2); + } else { + outfit.lookFeet = getNumber(L, 7); + outfit.lookLegs = getNumber(L, 6); + outfit.lookBody = getNumber(L, 5); + outfit.lookHead = getNumber(L, 4); + outfit.lookType = getNumber(L, 3); + outfit.lookTypeEx = getNumber(L, 2); + } + + ConditionOutfit* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + condition->setOutfit(outfit); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaConditionSetTiming(lua_State* L) +{ + // condition:setTiming(count) + int32_t count = getNumber(L, 2); + ConditionDamage* condition = dynamic_cast(getUserdata(L, 1)); + if (condition) { + if (condition->getType() == CONDITION_POISON) { + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + } else if (condition->getType() == CONDITION_FIRE) { + condition->setParam(CONDITION_PARAM_COUNT, 8); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 8); + + count /= 10; + } else if (condition->getType() == CONDITION_ENERGY) { + condition->setParam(CONDITION_PARAM_COUNT, 10); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 10); + + count /= 20; + } + + condition->setParam(CONDITION_PARAM_CYCLE, count); + + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + + +// MonsterType +int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) +{ + // MonsterType(name) + MonsterType* monsterType = g_monsters.getMonsterType(getString(L, 2)); + if (monsterType) { + pushUserdata(L, monsterType); + setMetatable(L, -1, "MonsterType"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) +{ + // monsterType:isAttackable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isAttackable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) +{ + // monsterType:isConvinceable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isConvinceable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) +{ + // monsterType:isSummonable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isSummonable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) +{ + // monsterType:isIllusionable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isIllusionable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) +{ + // monsterType:isHostile() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.isHostile); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeIsPushable(lua_State* L) +{ + // monsterType:isPushable() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.pushable); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) +{ + // monsterType:canPushItems() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.canPushItems); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeCanPushCreatures(lua_State* L) +{ + // monsterType:canPushCreatures() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushBoolean(L, monsterType->info.canPushCreatures); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetName(lua_State* L) +{ + // monsterType:getName() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushString(L, monsterType->name); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetNameDescription(lua_State* L) +{ + // monsterType:getNameDescription() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushString(L, monsterType->nameDescription); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetHealth(lua_State* L) +{ + // monsterType:getHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.health); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetMaxHealth(lua_State* L) +{ + // monsterType:getMaxHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.healthMax); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetRunHealth(lua_State* L) +{ + // monsterType:getRunHealth() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.runAwayHealth); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetExperience(lua_State* L) +{ + // monsterType:getExperience() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.experience); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCombatImmunities(lua_State* L) +{ + // monsterType:getCombatImmunities() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.damageImmunities); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetConditionImmunities(lua_State* L) +{ + // monsterType:getConditionImmunities() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.conditionImmunities); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) +{ + // monsterType:getAttackList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.attackSpells.size(), 0); + + int index = 0; + for (const auto& spellBlock : monsterType->info.attackSpells) { + lua_createtable(L, 0, 8); + + setField(L, "chance", spellBlock.chance); + setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "minCombatValue", spellBlock.minCombatValue); + setField(L, "maxCombatValue", spellBlock.maxCombatValue); + setField(L, "range", spellBlock.range); + pushUserdata(L, static_cast(spellBlock.spell)); + lua_setfield(L, -2, "spell"); + + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) +{ + // monsterType:getDefenseList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.defenseSpells.size(), 0); + + + int index = 0; + for (const auto& spellBlock : monsterType->info.defenseSpells) { + lua_createtable(L, 0, 8); + + setField(L, "chance", spellBlock.chance); + setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "minCombatValue", spellBlock.minCombatValue); + setField(L, "maxCombatValue", spellBlock.maxCombatValue); + setField(L, "range", spellBlock.range); + pushUserdata(L, static_cast(spellBlock.spell)); + lua_setfield(L, -2, "spell"); + + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) +{ + // monsterType:getElementList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, monsterType->info.elementMap.size(), 0); + for (const auto& elementEntry : monsterType->info.elementMap) { + lua_pushnumber(L, elementEntry.second); + lua_rawseti(L, -2, elementEntry.first); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) +{ + // monsterType:getVoices() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.voiceVector.size(), 0); + for (const auto& voiceBlock : monsterType->info.voiceVector) { + lua_createtable(L, 0, 2); + setField(L, "text", voiceBlock.text); + setField(L, "yellText", voiceBlock.yellText); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) +{ + // monsterType:getLoot() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + static const std::function&)> parseLoot = [&](const std::vector& lootList) { + lua_createtable(L, lootList.size(), 0); + + int index = 0; + for (const auto& lootBlock : lootList) { + lua_createtable(L, 0, 7); + + setField(L, "itemId", lootBlock.id); + setField(L, "chance", lootBlock.chance); + setField(L, "subType", lootBlock.subType); + setField(L, "maxCount", lootBlock.countmax); + setField(L, "actionId", lootBlock.actionId); + setField(L, "text", lootBlock.text); + + parseLoot(lootBlock.childLoot); + lua_setfield(L, -2, "childLoot"); + + lua_rawseti(L, -2, ++index); + } + }; + parseLoot(monsterType->info.lootItems); + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCreatureEvents(lua_State* L) +{ + // monsterType:getCreatureEvents() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.scripts.size(), 0); + for (const std::string& creatureEvent : monsterType->info.scripts) { + pushString(L, creatureEvent); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) +{ + // monsterType:getSummonList() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, monsterType->info.summons.size(), 0); + for (const auto& summonBlock : monsterType->info.summons) { + lua_createtable(L, 0, 3); + setField(L, "name", summonBlock.name); + setField(L, "chance", summonBlock.chance); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetMaxSummons(lua_State* L) +{ + // monsterType:getMaxSummons() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.maxSummons); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetArmor(lua_State* L) +{ + // monsterType:getArmor() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.armor); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetDefense(lua_State* L) +{ + // monsterType:getDefense() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.defense); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetOutfit(lua_State* L) +{ + // monsterType:getOutfit() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + pushOutfit(L, monsterType->info.outfit); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetRace(lua_State* L) +{ + // monsterType:getRace() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.race); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetCorpseId(lua_State* L) +{ + // monsterType:getCorpseId() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.lookcorpse); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetManaCost(lua_State* L) +{ + // monsterType:getManaCost() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.manaCost); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetBaseSpeed(lua_State* L) +{ + // monsterType:getBaseSpeed() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.baseSpeed); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetLight(lua_State* L) +{ + // monsterType:getLight() + MonsterType* monsterType = getUserdata(L, 1); + if (!monsterType) { + lua_pushnil(L); + return 1; + } + + lua_pushnumber(L, monsterType->info.light.level); + lua_pushnumber(L, monsterType->info.light.color); + return 2; +} + +int LuaScriptInterface::luaMonsterTypeGetTargetDistance(lua_State* L) +{ + // monsterType:getTargetDistance() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.targetDistance); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetChangeTargetChance(lua_State* L) +{ + // monsterType:getChangeTargetChance() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.changeTargetChance); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed(lua_State* L) +{ + // monsterType:getChangeTargetSpeed() + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + lua_pushnumber(L, monsterType->info.changeTargetSpeed); + } else { + lua_pushnil(L); + } + return 1; +} + +// Party +int LuaScriptInterface::luaPartyDisband(lua_State* L) +{ + // party:disband() + Party** partyPtr = getRawUserdata(L, 1); + if (partyPtr && *partyPtr) { + Party*& party = *partyPtr; + party->disband(); + party = nullptr; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetLeader(lua_State* L) +{ + // party:getLeader() + Party* party = getUserdata(L, 1); + if (!party) { + lua_pushnil(L); + return 1; + } + + Player* leader = party->getLeader(); + if (leader) { + pushUserdata(L, leader); + setMetatable(L, -1, "Player"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartySetLeader(lua_State* L) +{ + // party:setLeader(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->passPartyLeadership(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetMembers(lua_State* L) +{ + // party:getMembers() + Party* party = getUserdata(L, 1); + if (!party) { + lua_pushnil(L); + return 1; + } + + int index = 0; + lua_createtable(L, party->getMemberCount(), 0); + for (Player* player : party->getMembers()) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetMemberCount(lua_State* L) +{ + // party:getMemberCount() + Party* party = getUserdata(L, 1); + if (party) { + lua_pushnumber(L, party->getMemberCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetInvitees(lua_State* L) +{ + // party:getInvitees() + Party* party = getUserdata(L, 1); + if (party) { + lua_createtable(L, party->getInvitationCount(), 0); + + int index = 0; + for (Player* player : party->getInvitees()) { + pushUserdata(L, player); + setMetatable(L, -1, "Player"); + lua_rawseti(L, -2, ++index); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyGetInviteeCount(lua_State* L) +{ + // party:getInviteeCount() + Party* party = getUserdata(L, 1); + if (party) { + lua_pushnumber(L, party->getInvitationCount()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyAddInvite(lua_State* L) +{ + // party:addInvite(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->invitePlayer(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyRemoveInvite(lua_State* L) +{ + // party:removeInvite(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->removeInvite(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyAddMember(lua_State* L) +{ + // party:addMember(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->joinParty(*player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyRemoveMember(lua_State* L) +{ + // party:removeMember(player) + Player* player = getPlayer(L, 2); + Party* party = getUserdata(L, 1); + if (party && player) { + pushBoolean(L, party->leaveParty(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyIsSharedExperienceActive(lua_State* L) +{ + // party:isSharedExperienceActive() + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->isSharedExperienceActive()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyIsSharedExperienceEnabled(lua_State* L) +{ + // party:isSharedExperienceEnabled() + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->isSharedExperienceEnabled()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartyShareExperience(lua_State* L) +{ + // party:shareExperience(experience) + uint64_t experience = getNumber(L, 2); + Party* party = getUserdata(L, 1); + if (party) { + party->shareExperience(experience); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPartySetSharedExperience(lua_State* L) +{ + // party:setSharedExperience(active) + bool active = getBoolean(L, 2); + Party* party = getUserdata(L, 1); + if (party) { + pushBoolean(L, party->setSharedExperience(party->getLeader(), active)); + } else { + lua_pushnil(L); + } + return 1; +} + +// +LuaEnvironment::LuaEnvironment() : LuaScriptInterface("Main Interface") {} + +LuaEnvironment::~LuaEnvironment() +{ + delete testInterface; + closeState(); +} + +bool LuaEnvironment::initState() +{ + luaState = luaL_newstate(); + if (!luaState) { + return false; + } + + luaL_openlibs(luaState); + registerFunctions(); + + runningEventId = EVENT_ID_USER; + return true; +} + +bool LuaEnvironment::reInitState() +{ + // TODO: get children, reload children + closeState(); + return initState(); +} + +bool LuaEnvironment::closeState() +{ + if (!luaState) { + return false; + } + + for (const auto& combatEntry : combatIdMap) { + clearCombatObjects(combatEntry.first); + } + + for (const auto& areaEntry : areaIdMap) { + clearAreaObjects(areaEntry.first); + } + + for (auto& timerEntry : timerEvents) { + LuaTimerEventDesc timerEventDesc = std::move(timerEntry.second); + for (int32_t parameter : timerEventDesc.parameters) { + luaL_unref(luaState, LUA_REGISTRYINDEX, parameter); + } + luaL_unref(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + } + + combatIdMap.clear(); + areaIdMap.clear(); + timerEvents.clear(); + cacheFiles.clear(); + + lua_close(luaState); + luaState = nullptr; + return true; +} + +LuaScriptInterface* LuaEnvironment::getTestInterface() +{ + if (!testInterface) { + testInterface = new LuaScriptInterface("Test Interface"); + testInterface->initState(); + } + return testInterface; +} + +Combat* LuaEnvironment::getCombatObject(uint32_t id) const +{ + auto it = combatMap.find(id); + if (it == combatMap.end()) { + return nullptr; + } + return it->second; +} + +Combat* LuaEnvironment::createCombatObject(LuaScriptInterface* interface) +{ + Combat* combat = new Combat; + combatMap[++lastCombatId] = combat; + combatIdMap[interface].push_back(lastCombatId); + return combat; +} + +void LuaEnvironment::clearCombatObjects(LuaScriptInterface* interface) +{ + auto it = combatIdMap.find(interface); + if (it == combatIdMap.end()) { + return; + } + + for (uint32_t id : it->second) { + auto itt = combatMap.find(id); + if (itt != combatMap.end()) { + delete itt->second; + combatMap.erase(itt); + } + } + it->second.clear(); +} + +AreaCombat* LuaEnvironment::getAreaObject(uint32_t id) const +{ + auto it = areaMap.find(id); + if (it == areaMap.end()) { + return nullptr; + } + return it->second; +} + +uint32_t LuaEnvironment::createAreaObject(LuaScriptInterface* interface) +{ + areaMap[++lastAreaId] = new AreaCombat; + areaIdMap[interface].push_back(lastAreaId); + return lastAreaId; +} + +void LuaEnvironment::clearAreaObjects(LuaScriptInterface* interface) +{ + auto it = areaIdMap.find(interface); + if (it == areaIdMap.end()) { + return; + } + + for (uint32_t id : it->second) { + auto itt = areaMap.find(id); + if (itt != areaMap.end()) { + delete itt->second; + areaMap.erase(itt); + } + } + it->second.clear(); +} + +void LuaEnvironment::executeTimerEvent(uint32_t eventIndex) +{ + auto it = timerEvents.find(eventIndex); + if (it == timerEvents.end()) { + return; + } + + LuaTimerEventDesc timerEventDesc = std::move(it->second); + timerEvents.erase(it); + + //push function + lua_rawgeti(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + + //push parameters + for (auto parameter : boost::adaptors::reverse(timerEventDesc.parameters)) { + lua_rawgeti(luaState, LUA_REGISTRYINDEX, parameter); + } + + //call the function + if (reserveScriptEnv()) { + ScriptEnvironment* env = getScriptEnv(); + env->setTimerEvent(); + env->setScriptId(timerEventDesc.scriptId, this); + callFunction(timerEventDesc.parameters.size()); + } else { + std::cout << "[Error - LuaScriptInterface::executeTimerEvent] Call stack overflow" << std::endl; + } + + //free resources + luaL_unref(luaState, LUA_REGISTRYINDEX, timerEventDesc.function); + for (auto parameter : timerEventDesc.parameters) { + luaL_unref(luaState, LUA_REGISTRYINDEX, parameter); + } +} diff --git a/src/luascript.h b/src/luascript.h new file mode 100644 index 0000000..87a2df4 --- /dev/null +++ b/src/luascript.h @@ -0,0 +1,1241 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 +#define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 + +#include + +#if LUA_VERSION_NUM >= 502 +#ifndef LUA_COMPAT_ALL +#ifndef LUA_COMPAT_MODULE +#define luaL_register(L, libname, l) (luaL_newlib(L, l), lua_pushvalue(L, -1), lua_setglobal(L, libname)) +#endif +#undef lua_equal +#define lua_equal(L, i1, i2) lua_compare(L, (i1), (i2), LUA_OPEQ) +#endif +#endif + +#include "database.h" +#include "enums.h" +#include "position.h" + +class Thing; +class Creature; +class Player; +class Item; +class Container; +class AreaCombat; +class Combat; +class Condition; +class Npc; +class Monster; + +enum { + EVENT_ID_LOADING = 1, + EVENT_ID_USER = 1000, +}; + +enum LuaVariantType_t { + VARIANT_NONE, + + VARIANT_NUMBER, + VARIANT_POSITION, + VARIANT_TARGETPOSITION, + VARIANT_STRING, +}; + +enum LuaDataType { + LuaData_Unknown, + + LuaData_Item, + LuaData_Container, + LuaData_Teleport, + LuaData_Player, + LuaData_Monster, + LuaData_Npc, + LuaData_Tile, +}; + +struct LuaVariant { + LuaVariantType_t type = VARIANT_NONE; + std::string text; + Position pos; + uint32_t number = 0; +}; + +struct LuaTimerEventDesc { + int32_t scriptId = -1; + int32_t function = -1; + std::list parameters; + uint32_t eventId = 0; + + LuaTimerEventDesc() = default; + LuaTimerEventDesc(LuaTimerEventDesc&& other) = default; +}; + +class LuaScriptInterface; +class Cylinder; +class Game; +class Npc; + +class ScriptEnvironment +{ + public: + ScriptEnvironment(); + ~ScriptEnvironment(); + + // non-copyable + ScriptEnvironment(const ScriptEnvironment&) = delete; + ScriptEnvironment& operator=(const ScriptEnvironment&) = delete; + + void resetEnv(); + + void setScriptId(int32_t scriptId, LuaScriptInterface* scriptInterface) { + this->scriptId = scriptId; + interface = scriptInterface; + } + bool setCallbackId(int32_t callbackId, LuaScriptInterface* scriptInterface); + + int32_t getScriptId() const { + return scriptId; + } + LuaScriptInterface* getScriptInterface() { + return interface; + } + + void setTimerEvent() { + timerEvent = true; + } + + void getEventInfo(int32_t& scriptId, LuaScriptInterface*& scriptInterface, int32_t& callbackId, bool& timerEvent) const; + + void addTempItem(Item* item); + static void removeTempItem(Item* item); + uint32_t addThing(Thing* thing); + void insertItem(uint32_t uid, Item* item); + + static DBResult_ptr getResultByID(uint32_t id); + static uint32_t addResult(DBResult_ptr res); + static bool removeResult(uint32_t id); + + void setNpc(Npc* npc) { + curNpc = npc; + } + Npc* getNpc() const { + return curNpc; + } + + Thing* getThingByUID(uint32_t uid); + Item* getItemByUID(uint32_t uid); + Container* getContainerByUID(uint32_t uid); + void removeItemByUID(uint32_t uid); + + private: + typedef std::vector VariantVector; + typedef std::map StorageMap; + typedef std::map DBResultMap; + + LuaScriptInterface* interface; + + //for npc scripts + Npc* curNpc = nullptr; + + //temporary item list + static std::multimap tempItems; + + //local item map + std::unordered_map localMap; + uint32_t lastUID = std::numeric_limits::max(); + + //script file id + int32_t scriptId; + int32_t callbackId; + bool timerEvent; + + //result map + static uint32_t lastResultId; + static DBResultMap tempResults; +}; + +#define reportErrorFunc(a) reportError(__FUNCTION__, a, true) + +enum ErrorCode_t { + LUA_ERROR_PLAYER_NOT_FOUND, + LUA_ERROR_CREATURE_NOT_FOUND, + LUA_ERROR_ITEM_NOT_FOUND, + LUA_ERROR_THING_NOT_FOUND, + LUA_ERROR_TILE_NOT_FOUND, + LUA_ERROR_HOUSE_NOT_FOUND, + LUA_ERROR_COMBAT_NOT_FOUND, + LUA_ERROR_CONDITION_NOT_FOUND, + LUA_ERROR_AREA_NOT_FOUND, + LUA_ERROR_CONTAINER_NOT_FOUND, + LUA_ERROR_VARIANT_NOT_FOUND, + LUA_ERROR_VARIANT_UNKNOWN, + LUA_ERROR_SPELL_NOT_FOUND, +}; + +class LuaScriptInterface +{ + public: + explicit LuaScriptInterface(std::string interfaceName); + virtual ~LuaScriptInterface(); + + // non-copyable + LuaScriptInterface(const LuaScriptInterface&) = delete; + LuaScriptInterface& operator=(const LuaScriptInterface&) = delete; + + virtual bool initState(); + bool reInitState(); + + int32_t loadFile(const std::string& file, Npc* npc = nullptr); + + const std::string& getFileById(int32_t scriptId); + int32_t getEvent(const std::string& eventName); + int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); + + static ScriptEnvironment* getScriptEnv() { + assert(scriptEnvIndex >= 0 && scriptEnvIndex < 16); + return scriptEnv + scriptEnvIndex; + } + + static bool reserveScriptEnv() { + return ++scriptEnvIndex < 16; + } + + static void resetScriptEnv() { + assert(scriptEnvIndex >= 0); + scriptEnv[scriptEnvIndex--].resetEnv(); + } + + static void reportError(const char* function, const std::string& error_desc, bool stack_trace = false); + + const std::string& getInterfaceName() const { + return interfaceName; + } + const std::string& getLastLuaError() const { + return lastLuaError; + } + + lua_State* getLuaState() const { + return luaState; + } + + bool pushFunction(int32_t functionId); + + static int luaErrorHandler(lua_State* L); + bool callFunction(int params); + void callVoidFunction(int params); + + //push/pop common structures + static void pushThing(lua_State* L, Thing* thing); + static void pushVariant(lua_State* L, const LuaVariant& var); + static void pushString(lua_State* L, const std::string& value); + static void pushCallback(lua_State* L, int32_t callback); + static void pushCylinder(lua_State* L, Cylinder* cylinder); + + static std::string popString(lua_State* L); + static int32_t popCallback(lua_State* L); + + // Userdata + template + static void pushUserdata(lua_State* L, T* value) + { + T** userdata = static_cast(lua_newuserdata(L, sizeof(T*))); + *userdata = value; + } + + // Metatables + static void setMetatable(lua_State* L, int32_t index, const std::string& name); + static void setWeakMetatable(lua_State* L, int32_t index, const std::string& name); + + static void setItemMetatable(lua_State* L, int32_t index, const Item* item); + static void setCreatureMetatable(lua_State* L, int32_t index, const Creature* creature); + + // Get + template + inline static typename std::enable_if::value, T>::type + getNumber(lua_State* L, int32_t arg) + { + return static_cast(static_cast(lua_tonumber(L, arg))); + } + template + inline static typename std::enable_if::value || std::is_floating_point::value, T>::type + getNumber(lua_State* L, int32_t arg) + { + return static_cast(lua_tonumber(L, arg)); + } + template + static T getNumber(lua_State *L, int32_t arg, T defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; + } + return getNumber(L, arg); + } + template + static T* getUserdata(lua_State* L, int32_t arg) + { + T** userdata = getRawUserdata(L, arg); + if (!userdata) { + return nullptr; + } + return *userdata; + } + template + inline static T** getRawUserdata(lua_State* L, int32_t arg) + { + return static_cast(lua_touserdata(L, arg)); + } + + inline static bool getBoolean(lua_State* L, int32_t arg) + { + return lua_toboolean(L, arg) != 0; + } + inline static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) + { + const auto parameters = lua_gettop(L); + if (parameters == 0 || arg > parameters) { + return defaultValue; + } + return lua_toboolean(L, arg) != 0; + } + + static std::string getString(lua_State* L, int32_t arg); + static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); + static Position getPosition(lua_State* L, int32_t arg); + static Outfit_t getOutfit(lua_State* L, int32_t arg); + static LuaVariant getVariant(lua_State* L, int32_t arg); + + static Thing* getThing(lua_State* L, int32_t arg); + static Creature* getCreature(lua_State* L, int32_t arg); + static Player* getPlayer(lua_State* L, int32_t arg); + + template + static T getField(lua_State* L, int32_t arg, const std::string& key) + { + lua_getfield(L, arg, key.c_str()); + return getNumber(L, -1); + } + + static std::string getFieldString(lua_State* L, int32_t arg, const std::string& key); + + static LuaDataType getUserdataType(lua_State* L, int32_t arg); + + // Is + inline static bool isNumber(lua_State* L, int32_t arg) + { + return lua_type(L, arg) == LUA_TNUMBER; + } + inline static bool isString(lua_State* L, int32_t arg) + { + return lua_isstring(L, arg) != 0; + } + inline static bool isBoolean(lua_State* L, int32_t arg) + { + return lua_isboolean(L, arg); + } + inline static bool isTable(lua_State* L, int32_t arg) + { + return lua_istable(L, arg); + } + inline static bool isFunction(lua_State* L, int32_t arg) + { + return lua_isfunction(L, arg); + } + inline static bool isUserdata(lua_State* L, int32_t arg) + { + return lua_isuserdata(L, arg) != 0; + } + + // Push + static void pushBoolean(lua_State* L, bool value); + static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); + static void pushOutfit(lua_State* L, const Outfit_t& outfit); + + // + inline static void setField(lua_State* L, const char* index, lua_Number value) + { + lua_pushnumber(L, value); + lua_setfield(L, -2, index); + } + + inline static void setField(lua_State* L, const char* index, const std::string& value) + { + pushString(L, value); + lua_setfield(L, -2, index); + } + + static std::string escapeString(const std::string& string); + +#ifndef LUAJIT_VERSION + static const luaL_Reg luaBitReg[7]; +#endif + static const luaL_Reg luaConfigManagerTable[4]; + static const luaL_Reg luaDatabaseTable[9]; + static const luaL_Reg luaResultTable[6]; + + static int protectedCall(lua_State* L, int nargs, int nresults); + + protected: + virtual bool closeState(); + + void registerFunctions(); + + void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); + void registerTable(const std::string& tableName); + void registerMethod(const std::string& className, const std::string& methodName, lua_CFunction func); + void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); + void registerGlobalMethod(const std::string& functionName, lua_CFunction func); + void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); + void registerGlobalVariable(const std::string& name, lua_Number value); + void registerGlobalBoolean(const std::string& name, bool value); + + std::string getStackTrace(const std::string& error_desc); + + static std::string getErrorDesc(ErrorCode_t code); + static bool getArea(lua_State* L, std::list& list, uint32_t& rows); + + //lua functions + static int luaDoCreateItem(lua_State* L); + static int luaDoCreateItemEx(lua_State* L); + static int luaDoMoveCreature(lua_State* L); + + static int luaDoPlayerAddItem(lua_State* L); + static int luaDoTileAddItemEx(lua_State* L); + static int luaDoSetCreatureLight(lua_State* L); + + //get item info + static int luaGetDepotId(lua_State* L); + + //get creature info functions + static int luaGetPlayerFlagValue(lua_State* L); + static int luaGetCreatureCondition(lua_State* L); + + static int luaGetPlayerInstantSpellInfo(lua_State* L); + static int luaGetPlayerInstantSpellCount(lua_State* L); + + static int luaGetWorldTime(lua_State* L); + static int luaGetWorldLight(lua_State* L); + static int luaGetWorldUpTime(lua_State* L); + + //type validation + static int luaIsDepot(lua_State* L); + static int luaIsMoveable(lua_State* L); + static int luaIsValidUID(lua_State* L); + + //container + static int luaDoAddContainerItem(lua_State* L); + + // + static int luaCreateCombatArea(lua_State* L); + + static int luaDoAreaCombatHealth(lua_State* L); + static int luaDoTargetCombatHealth(lua_State* L); + + // + static int luaDoAreaCombatMana(lua_State* L); + static int luaDoTargetCombatMana(lua_State* L); + + static int luaDoAreaCombatCondition(lua_State* L); + static int luaDoTargetCombatCondition(lua_State* L); + + static int luaDoAreaCombatDispel(lua_State* L); + static int luaDoTargetCombatDispel(lua_State* L); + + static int luaDoChallengeCreature(lua_State* L); + + static int luaSetCreatureOutfit(lua_State* L); + static int luaSetMonsterOutfit(lua_State* L); + static int luaSetItemOutfit(lua_State* L); + + static int luaDebugPrint(lua_State* L); + static int luaIsInArray(lua_State* L); + static int luaAddEvent(lua_State* L); + static int luaStopEvent(lua_State* L); + + static int luaSaveServer(lua_State* L); + static int luaCleanMap(lua_State* L); + + static int luaIsInWar(lua_State* L); + + static int luaGetWaypointPositionByName(lua_State* L); + +#ifndef LUAJIT_VERSION + static int luaBitNot(lua_State* L); + static int luaBitAnd(lua_State* L); + static int luaBitOr(lua_State* L); + static int luaBitXor(lua_State* L); + static int luaBitLeftShift(lua_State* L); + static int luaBitRightShift(lua_State* L); +#endif + + static int luaConfigManagerGetString(lua_State* L); + static int luaConfigManagerGetNumber(lua_State* L); + static int luaConfigManagerGetBoolean(lua_State* L); + + static int luaDatabaseExecute(lua_State* L); + static int luaDatabaseAsyncExecute(lua_State* L); + static int luaDatabaseStoreQuery(lua_State* L); + static int luaDatabaseAsyncStoreQuery(lua_State* L); + static int luaDatabaseEscapeString(lua_State* L); + static int luaDatabaseEscapeBlob(lua_State* L); + static int luaDatabaseLastInsertId(lua_State* L); + static int luaDatabaseTableExists(lua_State* L); + + static int luaResultGetNumber(lua_State* L); + static int luaResultGetString(lua_State* L); + static int luaResultGetStream(lua_State* L); + static int luaResultNext(lua_State* L); + static int luaResultFree(lua_State* L); + + // Userdata + static int luaUserdataCompare(lua_State* L); + + // _G + static int luaIsType(lua_State* L); + static int luaRawGetMetatable(lua_State* L); + + // os + static int luaSystemTime(lua_State* L); + + // table + static int luaTableCreate(lua_State* L); + + // Game + static int luaGameGetSpectators(lua_State* L); + static int luaGameGetPlayers(lua_State* L); + static int luaGameLoadMap(lua_State* L); + + static int luaGameGetExperienceStage(lua_State* L); + static int luaGameGetMonsterCount(lua_State* L); + static int luaGameGetPlayerCount(lua_State* L); + static int luaGameGetNpcCount(lua_State* L); + + static int luaGameGetTowns(lua_State* L); + static int luaGameGetHouses(lua_State* L); + + static int luaGameGetGameState(lua_State* L); + static int luaGameSetGameState(lua_State* L); + + static int luaGameGetWorldType(lua_State* L); + static int luaGameSetWorldType(lua_State* L); + + static int luaGameGetReturnMessage(lua_State* L); + + static int luaGameCreateItem(lua_State* L); + static int luaGameCreateContainer(lua_State* L); + static int luaGameCreateMonster(lua_State* L); + static int luaGameCreateNpc(lua_State* L); + static int luaGameCreateTile(lua_State* L); + + static int luaGameStartRaid(lua_State* L); + + // Variant + static int luaVariantCreate(lua_State* L); + + static int luaVariantGetNumber(lua_State* L); + static int luaVariantGetString(lua_State* L); + static int luaVariantGetPosition(lua_State* L); + + // Position + static int luaPositionCreate(lua_State* L); + static int luaPositionAdd(lua_State* L); + static int luaPositionSub(lua_State* L); + static int luaPositionCompare(lua_State* L); + + static int luaPositionGetDistance(lua_State* L); + static int luaPositionIsSightClear(lua_State* L); + + static int luaPositionSendMagicEffect(lua_State* L); + static int luaPositionSendDistanceEffect(lua_State* L); + static int luaPositionSendMonsterSay(lua_State* L); + + // Tile + static int luaTileCreate(lua_State* L); + + static int luaTileGetPosition(lua_State* L); + static int luaTileGetGround(lua_State* L); + static int luaTileGetThing(lua_State* L); + static int luaTileGetThingCount(lua_State* L); + static int luaTileGetTopVisibleThing(lua_State* L); + + static int luaTileGetTopTopItem(lua_State* L); + static int luaTileGetTopDownItem(lua_State* L); + static int luaTileGetFieldItem(lua_State* L); + + static int luaTileGetItemById(lua_State* L); + static int luaTileGetItemByType(lua_State* L); + static int luaTileGetItemByTopOrder(lua_State* L); + static int luaTileGetItemCountById(lua_State* L); + + static int luaTileGetBottomCreature(lua_State* L); + static int luaTileGetTopCreature(lua_State* L); + static int luaTileGetBottomVisibleCreature(lua_State* L); + static int luaTileGetTopVisibleCreature(lua_State* L); + + static int luaTileGetItems(lua_State* L); + static int luaTileGetItemCount(lua_State* L); + static int luaTileGetDownItemCount(lua_State* L); + static int luaTileGetTopItemCount(lua_State* L); + + static int luaTileGetCreatures(lua_State* L); + static int luaTileGetCreatureCount(lua_State* L); + + static int luaTileHasProperty(lua_State* L); + static int luaTileHasFlag(lua_State* L); + + static int luaTileGetThingIndex(lua_State* L); + + static int luaTileQueryAdd(lua_State* L); + + static int luaTileGetHouse(lua_State* L); + + // NetworkMessage + static int luaNetworkMessageCreate(lua_State* L); + static int luaNetworkMessageDelete(lua_State* L); + + static int luaNetworkMessageGetByte(lua_State* L); + static int luaNetworkMessageGetU16(lua_State* L); + static int luaNetworkMessageGetU32(lua_State* L); + static int luaNetworkMessageGetU64(lua_State* L); + static int luaNetworkMessageGetString(lua_State* L); + static int luaNetworkMessageGetPosition(lua_State* L); + + static int luaNetworkMessageAddByte(lua_State* L); + static int luaNetworkMessageAddU16(lua_State* L); + static int luaNetworkMessageAddU32(lua_State* L); + static int luaNetworkMessageAddU64(lua_State* L); + static int luaNetworkMessageAddString(lua_State* L); + static int luaNetworkMessageAddPosition(lua_State* L); + static int luaNetworkMessageAddDouble(lua_State* L); + static int luaNetworkMessageAddItem(lua_State* L); + static int luaNetworkMessageAddItemId(lua_State* L); + + static int luaNetworkMessageReset(lua_State* L); + static int luaNetworkMessageSkipBytes(lua_State* L); + static int luaNetworkMessageSendToPlayer(lua_State* L); + + // Item + static int luaItemCreate(lua_State* L); + + static int luaItemIsItem(lua_State* L); + + static int luaItemGetParent(lua_State* L); + static int luaItemGetTopParent(lua_State* L); + + static int luaItemGetId(lua_State* L); + + static int luaItemClone(lua_State* L); + static int luaItemSplit(lua_State* L); + static int luaItemRemove(lua_State* L); + + static int luaItemGetMovementId(lua_State* L); + static int luaItemSetMovementId(lua_State* L); + static int luaItemGetActionId(lua_State* L); + static int luaItemSetActionId(lua_State* L); + static int luaItemGetUniqueId(lua_State* L); + + static int luaItemGetCount(lua_State* L); + static int luaItemGetCharges(lua_State* L); + static int luaItemGetFluidType(lua_State* L); + static int luaItemGetWeight(lua_State* L); + + static int luaItemGetSubType(lua_State* L); + + static int luaItemGetName(lua_State* L); + static int luaItemGetPluralName(lua_State* L); + static int luaItemGetArticle(lua_State* L); + + static int luaItemGetPosition(lua_State* L); + static int luaItemGetTile(lua_State* L); + + static int luaItemHasAttribute(lua_State* L); + static int luaItemGetAttribute(lua_State* L); + static int luaItemSetAttribute(lua_State* L); + static int luaItemRemoveAttribute(lua_State* L); + + static int luaItemMoveTo(lua_State* L); + static int luaItemTransform(lua_State* L); + static int luaItemDecay(lua_State* L); + + static int luaItemGetDescription(lua_State* L); + + static int luaItemHasProperty(lua_State* L); + + // Container + static int luaContainerCreate(lua_State* L); + + static int luaContainerGetSize(lua_State* L); + static int luaContainerGetCapacity(lua_State* L); + static int luaContainerGetEmptySlots(lua_State* L); + + static int luaContainerGetItemHoldingCount(lua_State* L); + static int luaContainerGetItemCountById(lua_State* L); + + static int luaContainerGetItem(lua_State* L); + static int luaContainerHasItem(lua_State* L); + static int luaContainerAddItem(lua_State* L); + static int luaContainerAddItemEx(lua_State* L); + + // Teleport + static int luaTeleportCreate(lua_State* L); + + static int luaTeleportGetDestination(lua_State* L); + static int luaTeleportSetDestination(lua_State* L); + + // Creature + static int luaCreatureCreate(lua_State* L); + + static int luaCreatureGetEvents(lua_State* L); + static int luaCreatureRegisterEvent(lua_State* L); + static int luaCreatureUnregisterEvent(lua_State* L); + + static int luaCreatureIsRemoved(lua_State* L); + static int luaCreatureIsCreature(lua_State* L); + static int luaCreatureIsInGhostMode(lua_State* L); + + static int luaCreatureCanSee(lua_State* L); + static int luaCreatureCanSeeCreature(lua_State* L); + + static int luaCreatureGetParent(lua_State* L); + + static int luaCreatureGetId(lua_State* L); + static int luaCreatureGetName(lua_State* L); + + static int luaCreatureGetTarget(lua_State* L); + static int luaCreatureSetTarget(lua_State* L); + + static int luaCreatureGetFollowCreature(lua_State* L); + static int luaCreatureSetFollowCreature(lua_State* L); + + static int luaCreatureGetMaster(lua_State* L); + static int luaCreatureSetMaster(lua_State* L); + + static int luaCreatureGetLight(lua_State* L); + static int luaCreatureSetLight(lua_State* L); + + static int luaCreatureGetSpeed(lua_State* L); + static int luaCreatureGetBaseSpeed(lua_State* L); + static int luaCreatureChangeSpeed(lua_State* L); + + static int luaCreatureSetDropLoot(lua_State* L); + + static int luaCreatureGetPosition(lua_State* L); + static int luaCreatureGetTile(lua_State* L); + static int luaCreatureGetDirection(lua_State* L); + static int luaCreatureSetDirection(lua_State* L); + + static int luaCreatureGetHealth(lua_State* L); + static int luaCreatureAddHealth(lua_State* L); + static int luaCreatureGetMaxHealth(lua_State* L); + static int luaCreatureSetMaxHealth(lua_State* L); + + static int luaCreatureGetMana(lua_State* L); + static int luaCreatureAddMana(lua_State* L); + static int luaCreatureGetMaxMana(lua_State* L); + + static int luaCreatureGetSkull(lua_State* L); + static int luaCreatureSetSkull(lua_State* L); + + static int luaCreatureGetOutfit(lua_State* L); + static int luaCreatureSetOutfit(lua_State* L); + + static int luaCreatureGetCondition(lua_State* L); + static int luaCreatureAddCondition(lua_State* L); + static int luaCreatureRemoveCondition(lua_State* L); + + static int luaCreatureRemove(lua_State* L); + static int luaCreatureTeleportTo(lua_State* L); + static int luaCreatureSay(lua_State* L); + + static int luaCreatureGetDamageMap(lua_State* L); + + static int luaCreatureGetSummons(lua_State* L); + + static int luaCreatureGetDescription(lua_State* L); + + static int luaCreatureGetPathTo(lua_State* L); + + // Player + static int luaPlayerCreate(lua_State* L); + + static int luaPlayerIsPlayer(lua_State* L); + + static int luaPlayerGetGuid(lua_State* L); + static int luaPlayerGetIp(lua_State* L); + static int luaPlayerGetAccountId(lua_State* L); + static int luaPlayerGetLastLoginSaved(lua_State* L); + static int luaPlayerGetLastLogout(lua_State* L); + static int luaPlayerHasFlag(lua_State* L); + + static int luaPlayerGetAccountType(lua_State* L); + static int luaPlayerSetAccountType(lua_State* L); + + static int luaPlayerGetCapacity(lua_State* L); + static int luaPlayerSetCapacity(lua_State* L); + + static int luaPlayerGetFreeCapacity(lua_State* L); + + static int luaPlayerGetDepotChest(lua_State* L); + + static int luaPlayerGetMurderTimestamps(lua_State* L); + static int luaPlayerGetPlayerKillerEnd(lua_State* L); + static int luaPlayerSetPlayerKillerEnd(lua_State* L); + static int luaPlayerGetDeathPenalty(lua_State* L); + + static int luaPlayerGetExperience(lua_State* L); + static int luaPlayerAddExperience(lua_State* L); + static int luaPlayerRemoveExperience(lua_State* L); + static int luaPlayerGetLevel(lua_State* L); + + static int luaPlayerGetMagicLevel(lua_State* L); + static int luaPlayerGetBaseMagicLevel(lua_State* L); + static int luaPlayerSetMaxMana(lua_State* L); + static int luaPlayerGetManaSpent(lua_State* L); + static int luaPlayerAddManaSpent(lua_State* L); + + static int luaPlayerGetBaseMaxHealth(lua_State* L); + static int luaPlayerGetBaseMaxMana(lua_State* L); + + static int luaPlayerGetSkillLevel(lua_State* L); + static int luaPlayerGetEffectiveSkillLevel(lua_State* L); + static int luaPlayerGetSkillPercent(lua_State* L); + static int luaPlayerGetSkillTries(lua_State* L); + static int luaPlayerAddSkillTries(lua_State* L); + + static int luaPlayerGetItemCount(lua_State* L); + static int luaPlayerGetItemById(lua_State* L); + + static int luaPlayerGetVocation(lua_State* L); + static int luaPlayerSetVocation(lua_State* L); + + static int luaPlayerGetSex(lua_State* L); + static int luaPlayerSetSex(lua_State* L); + + static int luaPlayerGetTown(lua_State* L); + static int luaPlayerSetTown(lua_State* L); + + static int luaPlayerGetGuild(lua_State* L); + static int luaPlayerSetGuild(lua_State* L); + + static int luaPlayerGetGuildLevel(lua_State* L); + static int luaPlayerSetGuildLevel(lua_State* L); + + static int luaPlayerGetGuildNick(lua_State* L); + static int luaPlayerSetGuildNick(lua_State* L); + + static int luaPlayerGetGroup(lua_State* L); + static int luaPlayerSetGroup(lua_State* L); + + static int luaPlayerGetSoul(lua_State* L); + static int luaPlayerAddSoul(lua_State* L); + static int luaPlayerGetMaxSoul(lua_State* L); + + static int luaPlayerGetBankBalance(lua_State* L); + static int luaPlayerSetBankBalance(lua_State* L); + + static int luaPlayerGetStorageValue(lua_State* L); + static int luaPlayerSetStorageValue(lua_State* L); + + static int luaPlayerAddItem(lua_State* L); + static int luaPlayerAddItemEx(lua_State* L); + static int luaPlayerRemoveItem(lua_State* L); + + static int luaPlayerGetMoney(lua_State* L); + static int luaPlayerAddMoney(lua_State* L); + static int luaPlayerRemoveMoney(lua_State* L); + + static int luaPlayerShowTextDialog(lua_State* L); + + static int luaPlayerSendTextMessage(lua_State* L); + static int luaPlayerSendPrivateMessage(lua_State* L); + + static int luaPlayerChannelSay(lua_State* L); + static int luaPlayerOpenChannel(lua_State* L); + + static int luaPlayerGetSlotItem(lua_State* L); + + static int luaPlayerGetParty(lua_State* L); + + static int luaPlayerSendOutfitWindow(lua_State* L); + + static int luaPlayerGetPremiumDays(lua_State* L); + static int luaPlayerAddPremiumDays(lua_State* L); + static int luaPlayerRemovePremiumDays(lua_State* L); + + static int luaPlayerHasBlessing(lua_State* L); + static int luaPlayerAddBlessing(lua_State* L); + static int luaPlayerRemoveBlessing(lua_State* L); + + static int luaPlayerCanLearnSpell(lua_State* L); + static int luaPlayerLearnSpell(lua_State* L); + static int luaPlayerForgetSpell(lua_State* L); + static int luaPlayerHasLearnedSpell(lua_State* L); + + static int luaPlayerSave(lua_State* L); + + static int luaPlayerIsPzLocked(lua_State* L); + + static int luaPlayerGetClient(lua_State* L); + static int luaPlayerGetHouse(lua_State* L); + + static int luaPlayerSetGhostMode(lua_State* L); + + static int luaPlayerGetContainerId(lua_State* L); + static int luaPlayerGetContainerById(lua_State* L); + static int luaPlayerGetContainerIndex(lua_State* L); + + static int luaPlayerGetTotalDamage(lua_State* L); + + // Monster + static int luaMonsterCreate(lua_State* L); + + static int luaMonsterIsMonster(lua_State* L); + + static int luaMonsterGetType(lua_State* L); + + static int luaMonsterGetSpawnPosition(lua_State* L); + static int luaMonsterIsInSpawnRange(lua_State* L); + + static int luaMonsterIsIdle(lua_State* L); + static int luaMonsterSetIdle(lua_State* L); + + static int luaMonsterIsTarget(lua_State* L); + static int luaMonsterIsOpponent(lua_State* L); + static int luaMonsterIsFriend(lua_State* L); + + static int luaMonsterAddFriend(lua_State* L); + static int luaMonsterRemoveFriend(lua_State* L); + static int luaMonsterGetFriendList(lua_State* L); + static int luaMonsterGetFriendCount(lua_State* L); + + static int luaMonsterAddTarget(lua_State* L); + static int luaMonsterRemoveTarget(lua_State* L); + static int luaMonsterGetTargetList(lua_State* L); + static int luaMonsterGetTargetCount(lua_State* L); + + static int luaMonsterSelectTarget(lua_State* L); + static int luaMonsterSearchTarget(lua_State* L); + + // Npc + static int luaNpcCreate(lua_State* L); + + static int luaNpcIsNpc(lua_State* L); + + static int luaNpcSetMasterPos(lua_State* L); + + // Guild + static int luaGuildCreate(lua_State* L); + + static int luaGuildGetId(lua_State* L); + static int luaGuildGetName(lua_State* L); + static int luaGuildGetMembersOnline(lua_State* L); + + static int luaGuildAddRank(lua_State* L); + static int luaGuildGetRankById(lua_State* L); + static int luaGuildGetRankByLevel(lua_State* L); + + // Group + static int luaGroupCreate(lua_State* L); + + static int luaGroupGetId(lua_State* L); + static int luaGroupGetName(lua_State* L); + static int luaGroupGetFlags(lua_State* L); + static int luaGroupGetAccess(lua_State* L); + static int luaGroupGetMaxDepotItems(lua_State* L); + static int luaGroupGetMaxVipEntries(lua_State* L); + + // Vocation + static int luaVocationCreate(lua_State* L); + + static int luaVocationGetId(lua_State* L); + static int luaVocationGetName(lua_State* L); + static int luaVocationGetDescription(lua_State* L); + + static int luaVocationGetRequiredSkillTries(lua_State* L); + static int luaVocationGetRequiredManaSpent(lua_State* L); + + static int luaVocationGetCapacityGain(lua_State* L); + + static int luaVocationGetHealthGain(lua_State* L); + static int luaVocationGetHealthGainTicks(lua_State* L); + static int luaVocationGetHealthGainAmount(lua_State* L); + + static int luaVocationGetManaGain(lua_State* L); + static int luaVocationGetManaGainTicks(lua_State* L); + static int luaVocationGetManaGainAmount(lua_State* L); + + static int luaVocationGetMaxSoul(lua_State* L); + static int luaVocationGetSoulGainTicks(lua_State* L); + + static int luaVocationGetAttackSpeed(lua_State* L); + static int luaVocationGetBaseSpeed(lua_State* L); + + static int luaVocationGetDemotion(lua_State* L); + static int luaVocationGetPromotion(lua_State* L); + + // Town + static int luaTownCreate(lua_State* L); + + static int luaTownGetId(lua_State* L); + static int luaTownGetName(lua_State* L); + static int luaTownGetTemplePosition(lua_State* L); + + // House + static int luaHouseCreate(lua_State* L); + + static int luaHouseGetId(lua_State* L); + static int luaHouseGetName(lua_State* L); + static int luaHouseGetTown(lua_State* L); + static int luaHouseGetExitPosition(lua_State* L); + static int luaHouseGetRent(lua_State* L); + + static int luaHouseGetOwnerGuid(lua_State* L); + static int luaHouseSetOwnerGuid(lua_State* L); + + static int luaHouseGetBeds(lua_State* L); + static int luaHouseGetBedCount(lua_State* L); + + static int luaHouseGetDoors(lua_State* L); + static int luaHouseGetDoorCount(lua_State* L); + + static int luaHouseGetTiles(lua_State* L); + static int luaHouseGetTileCount(lua_State* L); + + static int luaHouseGetAccessList(lua_State* L); + static int luaHouseSetAccessList(lua_State* L); + + // ItemType + static int luaItemTypeCreate(lua_State* L); + + static int luaItemTypeIsCorpse(lua_State* L); + static int luaItemTypeIsDoor(lua_State* L); + static int luaItemTypeIsContainer(lua_State* L); + static int luaItemTypeIsChest(lua_State* L); + static int luaItemTypeIsFluidContainer(lua_State* L); + static int luaItemTypeIsMovable(lua_State* L); + static int luaItemTypeIsRune(lua_State* L); + static int luaItemTypeIsStackable(lua_State* L); + static int luaItemTypeIsReadable(lua_State* L); + static int luaItemTypeIsWritable(lua_State* L); + static int luaItemTypeIsMagicField(lua_State* L); + static int luaItemTypeIsSplash(lua_State* L); + static int luaItemTypeIsKey(lua_State* L); + static int luaItemTypeIsDisguised(lua_State* L); + static int luaItemTypeIsDestroyable(lua_State* L); + static int luaItemTypeIsGroundTile(lua_State* L); + + static int luaItemTypeGetType(lua_State* L); + static int luaItemTypeGetId(lua_State* L); + static int luaItemTypeGetDisguiseId(lua_State* L); + static int luaItemTypeGetName(lua_State* L); + static int luaItemTypeGetPluralName(lua_State* L); + static int luaItemTypeGetArticle(lua_State* L); + static int luaItemTypeGetDescription(lua_State* L); + static int luaItemTypeGetSlotPosition(lua_State *L); + static int luaItemTypeGetDestroyTarget(lua_State* L); + + static int luaItemTypeGetCharges(lua_State* L); + static int luaItemTypeGetFluidSource(lua_State* L); + static int luaItemTypeGetCapacity(lua_State* L); + static int luaItemTypeGetWeight(lua_State* L); + + static int luaItemTypeGetShootRange(lua_State* L); + static int luaItemTypeGetAttack(lua_State* L); + static int luaItemTypeGetDefense(lua_State* L); + static int luaItemTypeGetArmor(lua_State* L); + static int luaItemTypeGetWeaponType(lua_State* L); + + static int luaItemTypeGetTransformEquipId(lua_State* L); + static int luaItemTypeGetTransformDeEquipId(lua_State* L); + static int luaItemTypeGetDecayId(lua_State* L); + static int luaItemTypeGetNutrition(lua_State* L); + static int luaItemTypeGetRequiredLevel(lua_State* L); + + static int luaItemTypeHasSubType(lua_State* L); + + // Combat + static int luaCombatCreate(lua_State* L); + + static int luaCombatSetParameter(lua_State* L); + static int luaCombatSetFormula(lua_State* L); + + static int luaCombatSetArea(lua_State* L); + static int luaCombatSetCondition(lua_State* L); + static int luaCombatSetCallback(lua_State* L); + + static int luaCombatExecute(lua_State* L); + + // Condition + static int luaConditionCreate(lua_State* L); + static int luaConditionDelete(lua_State* L); + + static int luaConditionGetId(lua_State* L); + static int luaConditionGetSubId(lua_State* L); + static int luaConditionGetType(lua_State* L); + static int luaConditionGetIcons(lua_State* L); + static int luaConditionGetEndTime(lua_State* L); + + static int luaConditionClone(lua_State* L); + + static int luaConditionGetTicks(lua_State* L); + static int luaConditionSetTicks(lua_State* L); + + static int luaConditionSetParameter(lua_State* L); + static int luaConditionSetSpeedDelta(lua_State* L); + static int luaConditionSetOutfit(lua_State* L); + + static int luaConditionSetTiming(lua_State* L); + + // MonsterType + static int luaMonsterTypeCreate(lua_State* L); + + static int luaMonsterTypeIsAttackable(lua_State* L); + static int luaMonsterTypeIsConvinceable(lua_State* L); + static int luaMonsterTypeIsSummonable(lua_State* L); + static int luaMonsterTypeIsIllusionable(lua_State* L); + static int luaMonsterTypeIsHostile(lua_State* L); + static int luaMonsterTypeIsPushable(lua_State* L); + + static int luaMonsterTypeCanPushItems(lua_State* L); + static int luaMonsterTypeCanPushCreatures(lua_State* L); + + static int luaMonsterTypeGetName(lua_State* L); + static int luaMonsterTypeGetNameDescription(lua_State* L); + + static int luaMonsterTypeGetHealth(lua_State* L); + static int luaMonsterTypeGetMaxHealth(lua_State* L); + static int luaMonsterTypeGetRunHealth(lua_State* L); + static int luaMonsterTypeGetExperience(lua_State* L); + + static int luaMonsterTypeGetCombatImmunities(lua_State* L); + static int luaMonsterTypeGetConditionImmunities(lua_State* L); + + static int luaMonsterTypeGetAttackList(lua_State* L); + static int luaMonsterTypeGetDefenseList(lua_State* L); + static int luaMonsterTypeGetElementList(lua_State* L); + + static int luaMonsterTypeGetVoices(lua_State* L); + static int luaMonsterTypeGetLoot(lua_State* L); + static int luaMonsterTypeGetCreatureEvents(lua_State* L); + + static int luaMonsterTypeGetSummonList(lua_State* L); + static int luaMonsterTypeGetMaxSummons(lua_State* L); + + static int luaMonsterTypeGetArmor(lua_State* L); + static int luaMonsterTypeGetDefense(lua_State* L); + static int luaMonsterTypeGetOutfit(lua_State* L); + static int luaMonsterTypeGetRace(lua_State* L); + static int luaMonsterTypeGetCorpseId(lua_State* L); + static int luaMonsterTypeGetManaCost(lua_State* L); + static int luaMonsterTypeGetBaseSpeed(lua_State* L); + static int luaMonsterTypeGetLight(lua_State* L); + + static int luaMonsterTypeGetTargetDistance(lua_State* L); + static int luaMonsterTypeGetChangeTargetChance(lua_State* L); + static int luaMonsterTypeGetChangeTargetSpeed(lua_State* L); + + // Party + static int luaPartyDisband(lua_State* L); + + static int luaPartyGetLeader(lua_State* L); + static int luaPartySetLeader(lua_State* L); + + static int luaPartyGetMembers(lua_State* L); + static int luaPartyGetMemberCount(lua_State* L); + + static int luaPartyGetInvitees(lua_State* L); + static int luaPartyGetInviteeCount(lua_State* L); + + static int luaPartyAddInvite(lua_State* L); + static int luaPartyRemoveInvite(lua_State* L); + + static int luaPartyAddMember(lua_State* L); + static int luaPartyRemoveMember(lua_State* L); + + static int luaPartyIsSharedExperienceActive(lua_State* L); + static int luaPartyIsSharedExperienceEnabled(lua_State* L); + static int luaPartyShareExperience(lua_State* L); + static int luaPartySetSharedExperience(lua_State* L); + + // + lua_State* luaState = nullptr; + std::string lastLuaError; + + std::string interfaceName; + int32_t eventTableRef = -1; + + static ScriptEnvironment scriptEnv[16]; + static int32_t scriptEnvIndex; + + int32_t runningEventId = EVENT_ID_USER; + std::string loadingFile; + + //script file cache + std::map cacheFiles; +}; + +class LuaEnvironment : public LuaScriptInterface +{ + public: + LuaEnvironment(); + ~LuaEnvironment(); + + // non-copyable + LuaEnvironment(const LuaEnvironment&) = delete; + LuaEnvironment& operator=(const LuaEnvironment&) = delete; + + bool initState(); + bool reInitState(); + bool closeState(); + + LuaScriptInterface* getTestInterface(); + + Combat* getCombatObject(uint32_t id) const; + Combat* createCombatObject(LuaScriptInterface* interface); + void clearCombatObjects(LuaScriptInterface* interface); + + AreaCombat* getAreaObject(uint32_t id) const; + uint32_t createAreaObject(LuaScriptInterface* interface); + void clearAreaObjects(LuaScriptInterface* interface); + + private: + void executeTimerEvent(uint32_t eventIndex); + + std::unordered_map timerEvents; + std::unordered_map combatMap; + std::unordered_map areaMap; + + std::unordered_map> combatIdMap; + std::unordered_map> areaIdMap; + + LuaScriptInterface* testInterface = nullptr; + + uint32_t lastEventTimerId = 1; + uint32_t lastCombatId = 0; + uint32_t lastAreaId = 0; + + friend class LuaScriptInterface; + friend class CombatSpell; +}; + +#endif diff --git a/src/mailbox.cpp b/src/mailbox.cpp new file mode 100644 index 0000000..23754b1 --- /dev/null +++ b/src/mailbox.cpp @@ -0,0 +1,183 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "mailbox.h" +#include "game.h" +#include "player.h" +#include "iologindata.h" +#include "town.h" + +extern Game g_game; + +ReturnValue Mailbox::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t, Creature*) const +{ + const Item* item = thing.getItem(); + if (item && Mailbox::canSend(item)) { + return RETURNVALUE_NOERROR; + } + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Mailbox::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const +{ + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; +} + +ReturnValue Mailbox::queryRemove(const Thing&, uint32_t, uint32_t) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +Cylinder* Mailbox::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) +{ + return this; +} + +void Mailbox::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Mailbox::addThing(int32_t, Thing* thing) +{ + Item* item = thing->getItem(); + if (item && Mailbox::canSend(item)) { + sendItem(item); + } +} + +void Mailbox::updateThing(Thing*, uint16_t, uint32_t) +{ + // +} + +void Mailbox::replaceThing(uint32_t, Thing*) +{ + // +} + +void Mailbox::removeThing(Thing*, uint32_t) +{ + // +} + +void Mailbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); +} + +bool Mailbox::sendItem(Item* item) const +{ + std::string receiver; + std::string townName; + if (!getDestination(item, receiver, townName)) { + return false; + } + + if (receiver.empty() || townName.empty()) { + return false; + } + + Town* town = g_game.map.towns.getTown(townName); + if (!town) { + return false; + } + + Player* player = g_game.getPlayerByName(receiver); + if (player) { + DepotLocker* depotLocker = player->getDepotLocker(town->getID(), true); + if (depotLocker) { + if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER, + item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + player->onReceiveMail(); + return true; + } + } + } else { + Player tmpPlayer(nullptr); + if (!IOLoginData::loadPlayerByName(&tmpPlayer, receiver)) { + return false; + } + + DepotLocker* depotLocker = tmpPlayer.getDepotLocker(town->getID(), true); + if (depotLocker) { + if (g_game.internalMoveItem(item->getParent(), depotLocker, INDEX_WHEREEVER, + item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { + g_game.transformItem(item, item->getID() + 1); + IOLoginData::savePlayer(&tmpPlayer); + return true; + } + } + } + + return false; +} + +bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) const +{ + const Container* container = item->getContainer(); + if (container) { + for (Item* containerItem : container->getItemList()) { + if (containerItem->getID() == ITEM_LABEL && getDestination(containerItem, name, town)) { + return true; + } + } + + return false; + } + + const std::string& text = item->getText(); + if (text.empty()) { + return false; + } + + std::istringstream iss(text, std::istringstream::in); + std::string temp; + uint32_t currentLine = 1; + + while (getline(iss, temp, '\n')) { + if (currentLine == 1) { + name = temp; + } else if (currentLine == 2) { + town = temp; + } else { + break; + } + + ++currentLine; + } + + trimString(name); + trimString(town); + return true; +} + +bool Mailbox::canSend(const Item* item) +{ + return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER; +} diff --git a/src/mailbox.h b/src/mailbox.h new file mode 100644 index 0000000..16ce6fb --- /dev/null +++ b/src/mailbox.h @@ -0,0 +1,66 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB +#define FS_MAILBOX_H_D231C6BE8D384CAAA3AE410C1323F9DB + +#include "item.h" +#include "cylinder.h" +#include "const.h" + +class Mailbox final : public Item, public Cylinder +{ + public: + explicit Mailbox(uint16_t itemId) : Item(itemId) {} + + Mailbox* getMailbox() final { + return this; + } + const Mailbox* getMailbox() const final { + return this; + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + private: + bool getDestination(Item* item, std::string& name, std::string& town) const; + bool sendItem(Item* item) const; + + static bool canSend(const Item* item); +}; + +#endif diff --git a/src/map.cpp b/src/map.cpp new file mode 100644 index 0000000..9e9f0f3 --- /dev/null +++ b/src/map.cpp @@ -0,0 +1,1028 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomap.h" +#include "iomapserialize.h" +#include "combat.h" +#include "creature.h" +#include "monster.h" +#include "game.h" + +extern Game g_game; + +bool Map::loadMap(const std::string& identifier, bool loadHouses) +{ + IOMap loader; + if (!loader.loadMap(this, identifier)) { + std::cout << "[Fatal - Map::loadMap] " << loader.getLastErrorString() << std::endl; + return false; + } + + Npcs::loadNpcs(); + if (!IOMap::loadSpawns(this)) { + std::cout << "[Warning - Map::loadMap] Failed to load spawn data." << std::endl; + } + + if (loadHouses) { + if (!IOMap::loadHouses(this)) { + std::cout << "[Warning - Map::loadMap] Failed to load house data." << std::endl; + } + + IOMapSerialize::loadHouseInfo(); + IOMapSerialize::loadHouseItems(this); + } + return true; +} + +bool Map::save() +{ + bool saved = false; + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize::saveHouseInfo()) { + saved = true; + break; + } + } + + if (!saved) { + return false; + } + + saved = false; + for (uint32_t tries = 0; tries < 3; tries++) { + if (IOMapSerialize::saveHouseItems()) { + saved = true; + break; + } + } + return saved; +} + +Tile* Map::getTile(uint16_t x, uint16_t y, uint8_t z) const +{ + if (z >= MAP_MAX_LAYERS) { + return nullptr; + } + + const QTreeLeafNode* leaf = QTreeNode::getLeafStatic(&root, x, y); + if (!leaf) { + return nullptr; + } + + const Floor* floor = leaf->getFloor(z); + if (!floor) { + return nullptr; + } + return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK]; +} + +void Map::setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile) +{ + if (z >= MAP_MAX_LAYERS) { + std::cout << "ERROR: Attempt to set tile on invalid coordinate " << Position(x, y, z) << "!" << std::endl; + return; + } + + QTreeLeafNode::newLeaf = false; + QTreeLeafNode* leaf = root.createLeaf(x, y, 15); + + if (QTreeLeafNode::newLeaf) { + //update north + QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE); + if (northLeaf) { + northLeaf->leafS = leaf; + } + + //update west leaf + QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y); + if (westLeaf) { + westLeaf->leafE = leaf; + } + + //update south + QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE); + if (southLeaf) { + leaf->leafS = southLeaf; + } + + //update east + QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y); + if (eastLeaf) { + leaf->leafE = eastLeaf; + } + } + + Floor* floor = leaf->createFloor(z); + uint32_t offsetX = x & FLOOR_MASK; + uint32_t offsetY = y & FLOOR_MASK; + + Tile*& tile = floor->tiles[offsetX][offsetY]; + if (tile) { + TileItemVector* items = newTile->getItemList(); + if (items) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + tile->addThing(*it); + } + items->clear(); + } + + Item* ground = newTile->getGround(); + if (ground) { + tile->addThing(ground); + newTile->setGround(nullptr); + } + delete newTile; + } else { + tile = newTile; + } +} + +bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos/* = false*/, bool forceLogin/* = false*/) +{ + bool foundTile; + bool placeInPZ; + + Tile* tile = getTile(centerPos.x, centerPos.y, centerPos.z); + if (tile) { + placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE); + + ReturnValue ret; + if (creature->getPlayer()) { + ret = tile->queryAdd(0, *creature, 1, 0); + } else { + ret = tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : FLAG_IGNOREBLOCKITEM)); + } + + foundTile = forceLogin || ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_PLAYERISNOTINVITED; + } else { + placeInPZ = false; + foundTile = false; + } + + if (!foundTile) { + static std::vector> extendedRelList { + {0, -2}, + {-1, -1}, {0, -1}, {1, -1}, + {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, + {-1, 1}, {0, 1}, {1, 1}, + {0, 2} + }; + + static std::vector> normalRelList { + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }; + + std::vector>& relList = (extendedPos ? extendedRelList : normalRelList); + + if (extendedPos) { + std::shuffle(relList.begin(), relList.begin() + 4, getRandomGenerator()); + std::shuffle(relList.begin() + 4, relList.end(), getRandomGenerator()); + } else { + std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); + } + + for (const auto& it : relList) { + Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); + + tile = getTile(tryPos.x, tryPos.y, tryPos.z); + if (!tile || (placeInPZ && !tile->hasFlag(TILESTATE_PROTECTIONZONE))) { + continue; + } + + if (tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : 0)) == RETURNVALUE_NOERROR) { + if (!extendedPos || isSightClear(centerPos, tryPos, false)) { + foundTile = true; + break; + } + } + } + + if (!foundTile) { + return false; + } + } + + int32_t index = 0; + uint32_t flags = 0; + Item* toItem = nullptr; + + Cylinder* toCylinder = tile->queryDestination(index, *creature, &toItem, flags); + toCylinder->internalAddThing(creature); + + const Position& dest = toCylinder->getPosition(); + getQTNode(dest.x, dest.y)->addCreature(creature); + return true; +} + +void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = false*/) +{ + Tile& oldTile = *creature.getTile(); + + Position oldPos = oldTile.getPosition(); + Position newPos = newTile.getPosition(); + + bool teleport = forceTeleport || !newTile.getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); + + SpectatorVec list; + getSpectators(list, oldPos, true); + getSpectators(list, newPos, true); + + std::vector oldStackPosVector; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + if (tmpPlayer->canSeeCreature(&creature)) { + oldStackPosVector.push_back(oldTile.getClientIndexOfCreature(tmpPlayer, &creature)); + } else { + oldStackPosVector.push_back(-1); + } + } + } + + //remove the creature + oldTile.removeThing(&creature, 0); + + QTreeLeafNode* leaf = getQTNode(oldPos.x, oldPos.y); + QTreeLeafNode* new_leaf = getQTNode(newPos.x, newPos.y); + + // Switch the node ownership + if (leaf != new_leaf) { + leaf->removeCreature(&creature); + new_leaf->addCreature(&creature); + } + + //add the creature + newTile.addThing(&creature); + + if (!teleport) { + if (oldPos.y > newPos.y) { + creature.setDirection(DIRECTION_NORTH); + } else if (oldPos.y < newPos.y) { + creature.setDirection(DIRECTION_SOUTH); + } + + if (oldPos.x < newPos.x) { + creature.setDirection(DIRECTION_EAST); + } else if (oldPos.x > newPos.x) { + creature.setDirection(DIRECTION_WEST); + } + } + + //send to client + size_t i = 0; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + //Use the correct stackpos + int32_t stackpos = oldStackPosVector[i++]; + if (stackpos != -1) { + tmpPlayer->sendCreatureMove(&creature, newPos, newTile.getStackposOfCreature(tmpPlayer, &creature), oldPos, stackpos, teleport); + } + } + } + + //event method + for (Creature* spectator : list) { + spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport); + } + + oldTile.postRemoveNotification(&creature, &newTile, 0); + newTile.postAddNotification(&creature, &oldTile, 0); +} + +void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const +{ + int_fast16_t min_y = centerPos.y + minRangeY; + int_fast16_t min_x = centerPos.x + minRangeX; + int_fast16_t max_y = centerPos.y + maxRangeY; + int_fast16_t max_x = centerPos.x + maxRangeX; + + int32_t minoffset = centerPos.getZ() - maxRangeZ; + uint16_t x1 = std::min(0xFFFF, std::max(0, (min_x + minoffset))); + uint16_t y1 = std::min(0xFFFF, std::max(0, (min_y + minoffset))); + + int32_t maxoffset = centerPos.getZ() - minRangeZ; + uint16_t x2 = std::min(0xFFFF, std::max(0, (max_x + maxoffset))); + uint16_t y2 = std::min(0xFFFF, std::max(0, (max_y + maxoffset))); + + int32_t startx1 = x1 - (x1 % FLOOR_SIZE); + int32_t starty1 = y1 - (y1 % FLOOR_SIZE); + int32_t endx2 = x2 - (x2 % FLOOR_SIZE); + int32_t endy2 = y2 - (y2 % FLOOR_SIZE); + + const QTreeLeafNode* startLeaf = QTreeNode::getLeafStatic(&root, startx1, starty1); + const QTreeLeafNode* leafS = startLeaf; + const QTreeLeafNode* leafE; + + for (int_fast32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { + leafE = leafS; + for (int_fast32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { + if (leafE) { + const CreatureVector& node_list = (onlyPlayers ? leafE->player_list : leafE->creature_list); + for (Creature* creature : node_list) { + const Position& cpos = creature->getPosition(); + if (minRangeZ > cpos.z || maxRangeZ < cpos.z) { + continue; + } + + int_fast16_t offsetZ = Position::getOffsetZ(centerPos, cpos); + if ((min_y + offsetZ) > cpos.y || (max_y + offsetZ) < cpos.y || (min_x + offsetZ) > cpos.x || (max_x + offsetZ) < cpos.x) { + continue; + } + + list.insert(creature); + } + leafE = leafE->leafE; + } else { + leafE = QTreeNode::getLeafStatic(&root, nx + FLOOR_SIZE, ny); + } + } + + if (leafS) { + leafS = leafS->leafS; + } else { + leafS = QTreeNode::getLeafStatic(&root, startx1, ny + FLOOR_SIZE); + } + } +} + +void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor /*= false*/, bool onlyPlayers /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) +{ + if (centerPos.z >= MAP_MAX_LAYERS) { + return; + } + + bool foundCache = false; + bool cacheResult = false; + + minRangeX = (minRangeX == 0 ? -maxViewportX : -minRangeX); + maxRangeX = (maxRangeX == 0 ? maxViewportX : maxRangeX); + minRangeY = (minRangeY == 0 ? -maxViewportY : -minRangeY); + maxRangeY = (maxRangeY == 0 ? maxViewportY : maxRangeY); + + if (minRangeX == -maxViewportX && maxRangeX == maxViewportX && minRangeY == -maxViewportY && maxRangeY == maxViewportY && multifloor) { + if (onlyPlayers) { + auto it = playersSpectatorCache.find(centerPos); + if (it != playersSpectatorCache.end()) { + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = it->second; + } + + foundCache = true; + } + } + + if (!foundCache) { + auto it = spectatorCache.find(centerPos); + if (it != spectatorCache.end()) { + if (!onlyPlayers) { + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); + } else { + list = it->second; + } + } else { + const SpectatorVec& cachedList = it->second; + for (Creature* spectator : cachedList) { + if (spectator->getPlayer()) { + list.insert(spectator); + } + } + } + + foundCache = true; + } else { + cacheResult = true; + } + } + } + + if (!foundCache) { + int32_t minRangeZ; + int32_t maxRangeZ; + + if (multifloor) { + if (centerPos.z > 7) { + //underground + + //8->15 + minRangeZ = std::max(centerPos.getZ() - 2, 0); + maxRangeZ = std::min(centerPos.getZ() + 2, MAP_MAX_LAYERS - 1); + } else if (centerPos.z == 6) { + minRangeZ = 0; + maxRangeZ = 8; + } else if (centerPos.z == 7) { + minRangeZ = 0; + maxRangeZ = 9; + } else { + minRangeZ = 0; + maxRangeZ = 7; + } + } else { + minRangeZ = centerPos.z; + maxRangeZ = centerPos.z; + } + + getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + + if (cacheResult) { + if (onlyPlayers) { + playersSpectatorCache[centerPos] = list; + } else { + spectatorCache[centerPos] = list; + } + } + } +} + +void Map::clearSpectatorCache() +{ + spectatorCache.clear(); + playersSpectatorCache.clear(); +} + +bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, + int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const +{ + //z checks + //underground 8->15 + //ground level and above 7->0 + if ((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8)) { + return false; + } + + int32_t deltaz = Position::getDistanceZ(fromPos, toPos); + if (deltaz > 2) { + return false; + } + + if ((Position::getDistanceX(fromPos, toPos) - deltaz) > rangex) { + return false; + } + + //distance checks + if ((Position::getDistanceY(fromPos, toPos) - deltaz) > rangey) { + return false; + } + + if (!checkLineOfSight) { + return true; + } + return isSightClear(fromPos, toPos, false); +} + +bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const +{ + if (fromPos == toPos) { + return true; + } + + Position start(fromPos.z > toPos.z ? toPos : fromPos); + Position destination(fromPos.z > toPos.z ? fromPos : toPos); + + const int8_t mx = start.x < destination.x ? 1 : start.x == destination.x ? 0 : -1; + const int8_t my = start.y < destination.y ? 1 : start.y == destination.y ? 0 : -1; + + int32_t A = Position::getOffsetY(destination, start); + int32_t B = Position::getOffsetX(start, destination); + int32_t C = -(A * destination.x + B * destination.y); + + while (start.x != destination.x || start.y != destination.y) { + int32_t move_hor = std::abs(A * (start.x + mx) + B * (start.y) + C); + int32_t move_ver = std::abs(A * (start.x) + B * (start.y + my) + C); + int32_t move_cross = std::abs(A * (start.x + mx) + B * (start.y + my) + C); + + if (start.y != destination.y && (start.x == destination.x || move_hor > move_ver || move_hor > move_cross)) { + start.y += my; + } + + if (start.x != destination.x && (start.y == destination.y || move_ver > move_hor || move_ver > move_cross)) { + start.x += mx; + } + + const Tile* tile = getTile(start.x, start.y, start.z); + if (tile && tile->hasProperty(CONST_PROP_BLOCKPROJECTILE)) { + return false; + } + } + + // now we need to perform a jump between floors to see if everything is clear (literally) + while (start.z != destination.z) { + const Tile* tile = getTile(start.x, start.y, start.z); + if (tile && tile->getThingCount() > 0) { + return false; + } + + start.z++; + } + + return true; +} + +bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const +{ + if (floorCheck && fromPos.z != toPos.z) { + return false; + } + + // Cast two converging rays and see if either yields a result. + return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); +} + +const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const +{ + int32_t walkCache = creature.getWalkCache(pos); + if (walkCache == 0) { + return nullptr; + } else if (walkCache == 1) { + return getTile(pos.x, pos.y, pos.z); + } + + //used for non-cached tiles + Tile* tile = getTile(pos.x, pos.y, pos.z); + if (creature.getTile() != tile) { + if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING) != RETURNVALUE_NOERROR) { + return nullptr; + } + } + return tile; +} + +bool Map::getPathMatching(const Creature& creature, std::forward_list& dirList, const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const +{ + Position pos = creature.getPosition(); + Position endPos; + + AStarNodes nodes(pos.x, pos.y); + + int32_t bestMatch = 0; + + static int_fast32_t dirNeighbors[8][5][2] = { + {{-1, 0}, {0, 1}, {1, 0}, {1, 1}, {-1, 1}}, + {{-1, 0}, {0, 1}, {0, -1}, {-1, -1}, {-1, 1}}, + {{-1, 0}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}}, + {{0, 1}, {1, 0}, {0, -1}, {1, -1}, {1, 1}}, + {{1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}}, + {{-1, 0}, {0, -1}, {-1, -1}, {1, -1}, {-1, 1}}, + {{0, 1}, {1, 0}, {1, -1}, {1, 1}, {-1, 1}}, + {{-1, 0}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}} + }; + static int_fast32_t allNeighbors[8][2] = { + {-1, 0}, {0, 1}, {1, 0}, {0, -1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1} + }; + + const Position startPos = pos; + + AStarNode* found = nullptr; + while (fpp.maxSearchDist != 0 || nodes.getClosedNodes() < 100) { + AStarNode* n = nodes.getBestNode(); + if (!n) { + if (found) { + break; + } + return false; + } + + const int_fast32_t x = n->x; + const int_fast32_t y = n->y; + pos.x = x; + pos.y = y; + if (pathCondition(startPos, pos, fpp, bestMatch)) { + found = n; + endPos = pos; + if (bestMatch == 0) { + break; + } + } + + uint_fast32_t dirCount; + int_fast32_t* neighbors; + if (n->parent) { + const int_fast32_t offset_x = n->parent->x - x; + const int_fast32_t offset_y = n->parent->y - y; + if (offset_y == 0) { + if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_WEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_EAST]; + } + } else if (!fpp.allowDiagonal || offset_x == 0) { + if (offset_y == -1) { + neighbors = *dirNeighbors[DIRECTION_NORTH]; + } else { + neighbors = *dirNeighbors[DIRECTION_SOUTH]; + } + } else if (offset_y == -1) { + if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_NORTHWEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_NORTHEAST]; + } + } else if (offset_x == -1) { + neighbors = *dirNeighbors[DIRECTION_SOUTHWEST]; + } else { + neighbors = *dirNeighbors[DIRECTION_SOUTHEAST]; + } + dirCount = fpp.allowDiagonal ? 5 : 3; + } else { + dirCount = 8; + neighbors = *allNeighbors; + } + + const int_fast32_t f = n->f; + for (uint_fast32_t i = 0; i < dirCount; ++i) { + pos.x = x + *neighbors++; + pos.y = y + *neighbors++; + + if (fpp.maxSearchDist != 0 && (Position::getDistanceX(startPos, pos) > fpp.maxSearchDist || Position::getDistanceY(startPos, pos) > fpp.maxSearchDist)) { + continue; + } + + if (fpp.keepDistance && !pathCondition.isInRange(startPos, pos, fpp)) { + continue; + } + + const Tile* tile; + AStarNode* neighborNode = nodes.getNodeByPosition(pos.x, pos.y); + if (neighborNode) { + tile = getTile(pos.x, pos.y, pos.z); + } else { + tile = canWalkTo(creature, pos); + if (!tile) { + continue; + } + } + + //The cost (g) for this neighbor + const int_fast32_t cost = AStarNodes::getMapWalkCost(n, pos); + const int_fast32_t extraCost = AStarNodes::getTileWalkCost(creature, tile); + const int_fast32_t newf = f + cost + extraCost; + + if (neighborNode) { + if (neighborNode->f <= newf) { + //The node on the closed/open list is cheaper than this one + continue; + } + + neighborNode->f = newf; + neighborNode->parent = n; + nodes.openNode(neighborNode); + } else { + //Does not exist in the open/closed list, create a new node + neighborNode = nodes.createOpenNode(n, pos.x, pos.y, newf); + if (!neighborNode) { + if (found) { + break; + } + return false; + } + } + } + + nodes.closeNode(n); + } + + if (!found) { + return false; + } + + int_fast32_t prevx = endPos.x; + int_fast32_t prevy = endPos.y; + + found = found->parent; + while (found) { + pos.x = found->x; + pos.y = found->y; + + int_fast32_t dx = pos.getX() - prevx; + int_fast32_t dy = pos.getY() - prevy; + + prevx = pos.x; + prevy = pos.y; + + if (dx == 1 && dy == 1) { + dirList.push_front(DIRECTION_NORTHWEST); + } else if (dx == -1 && dy == 1) { + dirList.push_front(DIRECTION_NORTHEAST); + } else if (dx == 1 && dy == -1) { + dirList.push_front(DIRECTION_SOUTHWEST); + } else if (dx == -1 && dy == -1) { + dirList.push_front(DIRECTION_SOUTHEAST); + } else if (dx == 1) { + dirList.push_front(DIRECTION_WEST); + } else if (dx == -1) { + dirList.push_front(DIRECTION_EAST); + } else if (dy == 1) { + dirList.push_front(DIRECTION_NORTH); + } else if (dy == -1) { + dirList.push_front(DIRECTION_SOUTH); + } + + found = found->parent; + } + return true; +} + +// AStarNodes + +AStarNodes::AStarNodes(uint32_t x, uint32_t y) + : nodes(), openNodes() +{ + curNode = 1; + closedNodes = 0; + openNodes[0] = true; + + AStarNode& startNode = nodes[0]; + startNode.parent = nullptr; + startNode.x = x; + startNode.y = y; + startNode.f = 0; + nodeTable[(x << 16) | y] = nodes; +} + +AStarNode* AStarNodes::createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f) +{ + if (curNode >= MAX_NODES) { + return nullptr; + } + + size_t retNode = curNode++; + openNodes[retNode] = true; + + AStarNode* node = nodes + retNode; + nodeTable[(x << 16) | y] = node; + node->parent = parent; + node->x = x; + node->y = y; + node->f = f; + return node; +} + +AStarNode* AStarNodes::getBestNode() +{ + if (curNode == 0) { + return nullptr; + } + + int32_t best_node_f = std::numeric_limits::max(); + int32_t best_node = -1; + for (size_t i = 0; i < curNode; i++) { + if (openNodes[i] && nodes[i].f < best_node_f) { + best_node_f = nodes[i].f; + best_node = i; + } + } + + if (best_node >= 0) { + return nodes + best_node; + } + return nullptr; +} + +void AStarNodes::closeNode(AStarNode* node) +{ + size_t index = node - nodes; + assert(index < MAX_NODES); + openNodes[index] = false; + ++closedNodes; +} + +void AStarNodes::openNode(AStarNode* node) +{ + size_t index = node - nodes; + assert(index < MAX_NODES); + if (!openNodes[index]) { + openNodes[index] = true; + --closedNodes; + } +} + +int_fast32_t AStarNodes::getClosedNodes() const +{ + return closedNodes; +} + +AStarNode* AStarNodes::getNodeByPosition(uint32_t x, uint32_t y) +{ + auto it = nodeTable.find((x << 16) | y); + if (it == nodeTable.end()) { + return nullptr; + } + return it->second; +} + +int_fast32_t AStarNodes::getMapWalkCost(AStarNode* node, const Position& neighborPos) +{ + if (std::abs(node->x - neighborPos.x) == std::abs(node->y - neighborPos.y)) { + //diagonal movement extra cost + return MAP_DIAGONALWALKCOST; + } + return MAP_NORMALWALKCOST; +} + +int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* tile) +{ + int_fast32_t cost = 0; + if (tile->getTopVisibleCreature(&creature) != nullptr) { + if (const Monster* monster = creature.getMonster()) { + if (monster->canPushCreatures()) { + return cost; + } + } + + //destroy creature cost + cost += MAP_NORMALWALKCOST * 3; + } + + if (const MagicField* field = tile->getFieldItem()) { + CombatType_t combatType = field->getCombatType(); + if (combatType != COMBAT_NONE) { + if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { + cost += MAP_NORMALWALKCOST * 18; + } + } + } + + return cost; +} + +// Floor +Floor::~Floor() +{ + for (auto& row : tiles) { + for (auto tile : row) { + delete tile; + } + } +} + +// QTreeNode +QTreeNode::~QTreeNode() +{ + for (auto* ptr : child) { + delete ptr; + } +} + +QTreeLeafNode* QTreeNode::getLeaf(uint32_t x, uint32_t y) +{ + if (leaf) { + return static_cast(this); + } + + QTreeNode* node = child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; + if (!node) { + return nullptr; + } + return node->getLeaf(x << 1, y << 1); +} + +QTreeLeafNode* QTreeNode::createLeaf(uint32_t x, uint32_t y, uint32_t level) +{ + if (!isLeaf()) { + uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); + if (!child[index]) { + if (level != FLOOR_BITS) { + child[index] = new QTreeNode(); + } else { + child[index] = new QTreeLeafNode(); + QTreeLeafNode::newLeaf = true; + } + } + return child[index]->createLeaf(x * 2, y * 2, level - 1); + } + return static_cast(this); +} + +// QTreeLeafNode +bool QTreeLeafNode::newLeaf = false; + +QTreeLeafNode::~QTreeLeafNode() +{ + for (auto* ptr : array) { + delete ptr; + } +} + +Floor* QTreeLeafNode::createFloor(uint32_t z) +{ + if (!array[z]) { + array[z] = new Floor(); + } + return array[z]; +} + +void QTreeLeafNode::addCreature(Creature* c) +{ + creature_list.push_back(c); + + if (c->getPlayer()) { + player_list.push_back(c); + } +} + +void QTreeLeafNode::removeCreature(Creature* c) +{ + auto iter = std::find(creature_list.begin(), creature_list.end(), c); + assert(iter != creature_list.end()); + *iter = creature_list.back(); + creature_list.pop_back(); + + if (c->getPlayer()) { + iter = std::find(player_list.begin(), player_list.end(), c); + assert(iter != player_list.end()); + *iter = player_list.back(); + player_list.pop_back(); + } +} + +uint32_t Map::clean() const +{ + uint64_t start = OTSYS_TIME(); + size_t count = 0, tiles = 0; + + if (g_game.getGameState() == GAME_STATE_NORMAL) { + g_game.setGameState(GAME_STATE_MAINTAIN); + } + + std::vector nodes { + &root + }; + std::vector toRemove; + do { + const QTreeNode* node = nodes.back(); + nodes.pop_back(); + if (node->isLeaf()) { + const QTreeLeafNode* leafNode = static_cast(node); + for (uint8_t z = 0; z < MAP_MAX_LAYERS; ++z) { + Floor* floor = leafNode->getFloor(z); + if (!floor) { + continue; + } + + for (auto& row : floor->tiles) { + for (auto tile : row) { + if (!tile || tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + continue; + } + + TileItemVector* itemList = tile->getItemList(); + if (!itemList) { + continue; + } + + ++tiles; + for (Item* item : *itemList) { + if (item->isCleanable()) { + toRemove.push_back(item); + } + } + + for (Item* item : toRemove) { + g_game.internalRemoveItem(item, -1); + } + count += toRemove.size(); + toRemove.clear(); + } + } + } + } else { + for (auto childNode : node->child) { + if (childNode) { + nodes.push_back(childNode); + } + } + } + } while (!nodes.empty()); + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + g_game.setGameState(GAME_STATE_NORMAL); + } + + std::cout << "> CLEAN: Removed " << count << " item" << (count != 1 ? "s" : "") + << " from " << tiles << " tile" << (tiles != 1 ? "s" : "") << " in " + << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return count; +} diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..67a26d2 --- /dev/null +++ b/src/map.h @@ -0,0 +1,287 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MAP_H_E3953D57C058461F856F5221D359DAFA +#define FS_MAP_H_E3953D57C058461F856F5221D359DAFA + +#include "position.h" +#include "item.h" +#include "fileloader.h" + +#include "tools.h" +#include "tile.h" +#include "town.h" +#include "house.h" +#include "spawn.h" + +class Creature; +class Player; +class Game; +class Tile; +class Map; + +static constexpr int32_t MAP_MAX_LAYERS = 16; + +struct FindPathParams; +struct AStarNode { + AStarNode* parent; + int_fast32_t f; + uint16_t x, y; +}; + +static constexpr int32_t MAX_NODES = 512; + +static constexpr int32_t MAP_NORMALWALKCOST = 10; +static constexpr int32_t MAP_DIAGONALWALKCOST = 25; + +class AStarNodes +{ + public: + AStarNodes(uint32_t x, uint32_t y); + + AStarNode* createOpenNode(AStarNode* parent, uint32_t x, uint32_t y, int_fast32_t f); + AStarNode* getBestNode(); + void closeNode(AStarNode* node); + void openNode(AStarNode* node); + int_fast32_t getClosedNodes() const; + AStarNode* getNodeByPosition(uint32_t x, uint32_t y); + + static int_fast32_t getMapWalkCost(AStarNode* node, const Position& neighborPos); + static int_fast32_t getTileWalkCost(const Creature& creature, const Tile* tile); + + private: + AStarNode nodes[MAX_NODES]; + bool openNodes[MAX_NODES]; + std::unordered_map nodeTable; + size_t curNode; + int_fast32_t closedNodes; +}; + +typedef std::map SpectatorCache; + +static constexpr int32_t FLOOR_BITS = 3; +static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS); +static constexpr int32_t FLOOR_MASK = (FLOOR_SIZE - 1); + +struct Floor { + constexpr Floor() = default; + ~Floor(); + + // non-copyable + Floor(const Floor&) = delete; + Floor& operator=(const Floor&) = delete; + + Tile* tiles[FLOOR_SIZE][FLOOR_SIZE] = {}; +}; + +class FrozenPathingConditionCall; +class QTreeLeafNode; + +class QTreeNode +{ + public: + constexpr QTreeNode() = default; + virtual ~QTreeNode(); + + // non-copyable + QTreeNode(const QTreeNode&) = delete; + QTreeNode& operator=(const QTreeNode&) = delete; + + bool isLeaf() const { + return leaf; + } + + QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); + + template + inline static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) + { + do { + node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; + if (!node) { + return nullptr; + } + + x <<= 1; + y <<= 1; + } while (!node->leaf); + return static_cast(node); + } + + QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); + + protected: + QTreeNode* child[4] = {}; + + bool leaf = false; + + friend class Map; +}; + +class QTreeLeafNode final : public QTreeNode +{ + public: + QTreeLeafNode() { leaf = true; newLeaf = true; } + ~QTreeLeafNode(); + + // non-copyable + QTreeLeafNode(const QTreeLeafNode&) = delete; + QTreeLeafNode& operator=(const QTreeLeafNode&) = delete; + + Floor* createFloor(uint32_t z); + Floor* getFloor(uint8_t z) const { + return array[z]; + } + + void addCreature(Creature* c); + void removeCreature(Creature* c); + + protected: + static bool newLeaf; + QTreeLeafNode* leafS = nullptr; + QTreeLeafNode* leafE = nullptr; + Floor* array[MAP_MAX_LAYERS] = {}; + CreatureVector creature_list; + CreatureVector player_list; + + friend class Map; + friend class QTreeNode; +}; + +/** + * Map class. + * Holds all the actual map-data + */ + +class Map +{ + public: + static constexpr int32_t maxViewportX = 11; //min value: maxClientViewportX + 1 + static constexpr int32_t maxViewportY = 11; //min value: maxClientViewportY + 1 + static constexpr int32_t maxClientViewportX = 8; + static constexpr int32_t maxClientViewportY = 6; + + uint32_t clean() const; + + /** + * Load a map. + * \returns true if the map was loaded successfully + */ + bool loadMap(const std::string& identifier, bool loadHouses); + + /** + * Save a map. + * \returns true if the map was saved successfully + */ + static bool save(); + + /** + * Get a single tile. + * \returns A pointer to that tile. + */ + Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const; + inline Tile* getTile(const Position& pos) const { + return getTile(pos.x, pos.y, pos.z); + } + + /** + * Set a single tile. + */ + void setTile(uint16_t x, uint16_t y, uint8_t z, Tile* newTile); + void setTile(const Position& pos, Tile* newTile) { + setTile(pos.x, pos.y, pos.z, newTile); + } + + /** + * Place a creature on the map + * \param centerPos The position to place the creature + * \param creature Creature to place on the map + * \param extendedPos If true, the creature will in first-hand be placed 2 tiles away + * \param forceLogin If true, placing the creature will not fail becase of obstacles (creatures/chests) + */ + bool placeCreature(const Position& centerPos, Creature* creature, bool extendedPos = false, bool forceLogin = false); + + void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); + + void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, + int32_t minRangeX = 0, int32_t maxRangeX = 0, + int32_t minRangeY = 0, int32_t maxRangeY = 0); + + void clearSpectatorCache(); + + /** + * Checks if you can throw an object to that position + * \param fromPos from Source point + * \param toPos Destination point + * \param rangex maximum allowed range horizontially + * \param rangey maximum allowed range vertically + * \param checkLineOfSight checks if there is any blocking objects in the way + * \returns The result if you can throw there or not + */ + bool canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight = true, + int32_t rangex = Map::maxClientViewportX, int32_t rangey = Map::maxClientViewportY) const; + + /** + * Checks if path is clear from fromPos to toPos + * Notice: This only checks a straight line if the path is clear, for path finding use getPathTo. + * \param fromPos from Source point + * \param toPos Destination point + * \param floorCheck if true then view is not clear if fromPos.z is not the same as toPos.z + * \returns The result if there is no obstacles + */ + bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; + bool checkSightLine(const Position& fromPos, const Position& toPos) const; + + const Tile* canWalkTo(const Creature& creature, const Position& pos) const; + + bool getPathMatching(const Creature& creature, std::forward_list& dirList, + const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) const; + + std::map waypoints; + + QTreeLeafNode* getQTNode(uint16_t x, uint16_t y) { + return QTreeNode::getLeafStatic(&root, x, y); + } + + Spawns spawns; + Towns towns; + Houses houses; + protected: + SpectatorCache spectatorCache; + SpectatorCache playersSpectatorCache; + + QTreeNode root; + + std::string spawnfile; + std::string housefile; + + uint32_t width = 0; + uint32_t height = 0; + + // Actually scans the map for spectators + void getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, + int32_t minRangeX, int32_t maxRangeX, + int32_t minRangeY, int32_t maxRangeY, + int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const; + + friend class Game; + friend class IOMap; +}; + +#endif diff --git a/src/monster.cpp b/src/monster.cpp new file mode 100644 index 0000000..f0bdc54 --- /dev/null +++ b/src/monster.cpp @@ -0,0 +1,2165 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "monster.h" +#include "game.h" +#include "spells.h" +#include "configmanager.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Monsters g_monsters; + +int32_t Monster::despawnRange; +int32_t Monster::despawnRadius; + +uint32_t Monster::monsterAutoID = 0x40000000; + +Monster* Monster::createMonster(const std::string& name) +{ + MonsterType* mType = g_monsters.getMonsterType(name); + if (!mType) { + return nullptr; + } + return new Monster(mType); +} + +Monster::Monster(MonsterType* mtype) : + Creature(), + strDescription(asLowerCaseString(mtype->nameDescription)), + mType(mtype) +{ + defaultOutfit = mType->info.outfit; + currentOutfit = mType->info.outfit; + skull = mType->info.skull; + health = mType->info.health; + healthMax = mType->info.healthMax; + baseSpeed = mType->info.baseSpeed; + internalLight = mType->info.light; + + // register creature events + for (const std::string& scriptName : mType->info.scripts) { + if (!registerCreatureEvent(scriptName)) { + std::cout << "[Warning - Monster::Monster] Unknown event name: " << scriptName << std::endl; + } + } +} + +Monster::~Monster() +{ + clearTargetList(); + clearFriendList(); +} + +void Monster::addList() +{ + g_game.addMonster(this); +} + +void Monster::removeList() +{ + g_game.removeMonster(this); +} + +int32_t Monster::getDefense() +{ + int32_t totalDefense = mType->info.defense + 1; + int32_t defenseSkill = mType->info.skill; + + fightMode_t attackMode = FIGHTMODE_BALANCED; + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; +} + +bool Monster::canSee(const Position& pos) const +{ + return Creature::canSee(getPosition(), pos, 9, 9); +} + +void Monster::onAttackedCreature(Creature* creature) +{ + if (isSummon() && getMaster()) { + master->onAttackedCreature(creature); + } +} + +void Monster::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (mType->info.creatureAppearEvent != -1) { + // onCreatureAppear(self, creature) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureAppear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureAppearEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureAppearEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (creature == this) { + //We just spawned lets look around to see who is there. + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + onCreatureEnter(creature); + } +} + +void Monster::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (mType->info.creatureDisappearEvent != -1) { + // onCreatureDisappear(self, creature) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureDisappear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureDisappearEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureDisappearEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (creature == this) { + if (spawn) { + spawn->startSpawnCheck(); + } + + setIdle(true); + } else { + onCreatureLeave(creature); + } +} + +void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (mType->info.creatureMoveEvent != -1) { + // onCreatureMove(self, creature, oldPosition, newPosition) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureMove] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureMoveEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureMoveEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushPosition(L, oldPos); + LuaScriptInterface::pushPosition(L, newPos); + + if (scriptInterface->callFunction(4)) { + return; + } + } + + if (creature == this) { + if (isSummon()) { + isMasterInRange = canSee(getMaster()->getPosition()); + } + + updateTargetList(); + updateIdleStatus(); + } else { + bool canSeeNewPos = canSee(newPos); + bool canSeeOldPos = canSee(oldPos); + + if (canSeeNewPos && !canSeeOldPos) { + onCreatureEnter(creature); + } else if (!canSeeNewPos && canSeeOldPos) { + onCreatureLeave(creature); + } + + if (canSeeNewPos && isSummon() && getMaster() == creature) { + isMasterInRange = true; //Follow master again + } + + updateIdleStatus(); + + if (!isSummon()) { + if (followCreature) { + const Position& followPosition = followCreature->getPosition(); + const Position& position = getPosition(); + + int32_t offset_x = Position::getDistanceX(followPosition, position); + int32_t offset_y = Position::getDistanceY(followPosition, position); + if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0 && targetChangeCooldown <= 0) { + Direction dir = getDirectionTo(position, followPosition); + const Position& checkPosition = getNextPosition(dir, position); + + Tile* tile = g_game.map.getTile(checkPosition); + if (tile) { + Creature* topCreature = tile->getTopCreature(); + if (topCreature && followCreature != topCreature && isOpponent(topCreature)) { + selectTarget(topCreature); + } + } + } + } else if (isOpponent(creature)) { + //we have no target lets try pick this one + selectTarget(creature); + } + } + } +} + +void Monster::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) +{ + Creature::onCreatureSay(creature, type, text); + + if (mType->info.creatureSayEvent != -1) { + // onCreatureSay(self, creature, type, message) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onCreatureSay] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.creatureSayEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.creatureSayEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + lua_pushnumber(L, type); + LuaScriptInterface::pushString(L, text); + + scriptInterface->callVoidFunction(4); + } +} + +void Monster::addFriend(Creature* creature) +{ + assert(creature != this); + auto result = friendList.insert(creature); + if (result.second) { + creature->incrementReferenceCounter(); + } +} + +void Monster::removeFriend(Creature* creature) +{ + auto it = friendList.find(creature); + if (it != friendList.end()) { + creature->decrementReferenceCounter(); + friendList.erase(it); + } +} + +void Monster::addTarget(Creature* creature, bool pushFront/* = false*/) +{ + assert(creature != this); + if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { + creature->incrementReferenceCounter(); + if (pushFront) { + targetList.push_front(creature); + } else { + targetList.push_back(creature); + } + } +} + +void Monster::removeTarget(Creature* creature) +{ + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it != targetList.end()) { + creature->decrementReferenceCounter(); + targetList.erase(it); + } +} + +void Monster::updateTargetList() +{ + auto friendIterator = friendList.begin(); + while (friendIterator != friendList.end()) { + Creature* creature = *friendIterator; + if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + creature->decrementReferenceCounter(); + friendIterator = friendList.erase(friendIterator); + } else { + ++friendIterator; + } + } + + auto targetIterator = targetList.begin(); + while (targetIterator != targetList.end()) { + Creature* creature = *targetIterator; + if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { + creature->decrementReferenceCounter(); + targetIterator = targetList.erase(targetIterator); + } else { + ++targetIterator; + } + } + + SpectatorVec list; + g_game.map.getSpectators(list, position, true); + list.erase(this); + for (Creature* spectator : list) { + if (canSee(spectator->getPosition())) { + onCreatureFound(spectator); + } + } +} + +void Monster::clearTargetList() +{ + for (Creature* creature : targetList) { + creature->decrementReferenceCounter(); + } + targetList.clear(); +} + +void Monster::clearFriendList() +{ + for (Creature* creature : friendList) { + creature->decrementReferenceCounter(); + } + friendList.clear(); +} + +void Monster::onCreatureFound(Creature* creature, bool pushFront/* = false*/) +{ + if (isFriend(creature)) { + addFriend(creature); + } + + if (isOpponent(creature)) { + addTarget(creature, pushFront); + } + + updateIdleStatus(); +} + +void Monster::onCreatureEnter(Creature* creature) +{ + // std::cout << "onCreatureEnter - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Follow master again + isMasterInRange = true; + } + + onCreatureFound(creature, true); +} + +bool Monster::isFriend(const Creature* creature) const +{ + if (isSummon() && getMaster()->getPlayer()) { + const Player* masterPlayer = getMaster()->getPlayer(); + const Player* tmpPlayer = nullptr; + + if (creature->getPlayer()) { + tmpPlayer = creature->getPlayer(); + } else { + const Creature* creatureMaster = creature->getMaster(); + + if (creatureMaster && creatureMaster->getPlayer()) { + tmpPlayer = creatureMaster->getPlayer(); + } + } + + if (tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) { + return true; + } + } else if (creature->getMonster() && !creature->isSummon()) { + return true; + } + + return false; +} + +bool Monster::isOpponent(const Creature* creature) const +{ + if (isSummon() && getMaster()->getPlayer()) { + if (creature != getMaster()) { + return true; + } + } else { + if ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || + (creature->getMaster() && creature->getMaster()->getPlayer())) { + return true; + } + } + + return false; +} + +void Monster::onCreatureLeave(Creature* creature) +{ + // std::cout << "onCreatureLeave - " << creature->getName() << std::endl; + + if (getMaster() == creature) { + //Take random steps and only use defense abilities (e.g. heal) until its master comes back + isMasterInRange = false; + } + + //update friendList + if (isFriend(creature)) { + removeFriend(creature); + } + + //update targetList + if (isOpponent(creature)) { + removeTarget(creature); + if (targetList.empty()) { + updateIdleStatus(); + } + } +} + +bool Monster::searchTarget(TargetSearchType_t searchType) +{ + std::list resultList; + const Position& myPos = getPosition(); + + for (Creature* creature : targetList) { + if (followCreature != creature && isTarget(creature)) { + if (searchType == TARGETSEARCH_ANY || canUseAttack(myPos, creature)) { + resultList.push_back(creature); + } + } + } + + switch (searchType) { + case TARGETSEARCH_NEAREST: { + Creature* target = nullptr; + + int32_t minRange = 0; + if (attackedCreature) { + const Position& targetPosition = attackedCreature->getPosition(); + minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + } + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + const Position& targetPosition = creature->getPosition(); + + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + if (distance < minRange) { + target = creature; + minRange = distance; + } + } + } else { + for (Creature* creature : targetList) { + if (!isTarget(creature)) { + continue; + } + + const Position& targetPosition = creature->getPosition(); + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + if (distance < minRange) { + target = creature; + minRange = distance; + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + case TARGETSEARCH_WEAKEST: { + Creature* target = nullptr; + + int32_t health = 0; + if (attackedCreature) { + health = attackedCreature->getMaxHealth(); + } + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } else { + for (Creature* creature : targetList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + case TARGETSEARCH_MOSTDAMAGE: { + Creature* target = nullptr; + + int32_t maxDamage = 0; + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } else { + for (Creature* creature : targetList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + default: { + if (!resultList.empty()) { + auto it = resultList.begin(); + std::advance(it, uniform_random(0, resultList.size() - 1)); + return selectTarget(*it); + } + + break; + } + } + + //lets just pick the first target in the list if we do not have a target + if (!attackedCreature) { + for (Creature* target : targetList) { + if (followCreature != target && selectTarget(target)) { + return true; + } + } + } + + return false; +} + +void Monster::onFollowCreatureComplete(const Creature* creature) +{ + if (creature) { + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it != targetList.end()) { + Creature* target = (*it); + targetList.erase(it); + + if (hasFollowPath) { + targetList.push_front(target); + } else if (!isSummon()) { + targetList.push_back(target); + } else { + target->decrementReferenceCounter(); + } + } + } +} + +BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); + + if (damage != 0) { + int32_t elementMod = 0; + auto it = mType->info.elementMap.find(combatType); + if (it != mType->info.elementMap.end()) { + elementMod = it->second; + } + + if (elementMod != 0) { + damage = static_cast(std::round(damage * ((100 - elementMod) / 100.))); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + } + + return blockType; +} + + +bool Monster::isTarget(const Creature* creature) const +{ + if (creature->isRemoved() || !creature->isAttackable() || + creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) { + return false; + } + + if (creature->getPosition().z != getPosition().z) { + return false; + } + return true; +} + +bool Monster::selectTarget(Creature* creature) +{ + if (!isTarget(creature)) { + return false; + } + + auto it = std::find(targetList.begin(), targetList.end(), creature); + if (it == targetList.end()) { + //Target not found in our target list. + return false; + } + + if (isHostile() || isSummon()) { + if (setAttackedCreature(creature) && !isSummon()) { + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + } + + // without this task, monster would randomly start dancing until next game round + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + targetChangeCooldown += 3000; + return setFollowCreature(creature); +} + +void Monster::setIdle(bool idle) +{ + if (isRemoved() || getHealth() <= 0) { + return; + } + + isIdle = idle; + + if (!isIdle) { + g_game.addCreatureCheck(this); + } else { + onIdleStatus(); + clearTargetList(); + clearFriendList(); + Game::removeCreatureCheck(this); + } +} + +void Monster::updateIdleStatus() +{ + bool idle = false; + + if (conditions.empty()) { + if (!isSummon() && targetList.empty()) { + idle = true; + } + } + + setIdle(idle); +} + +void Monster::onAddCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onEndCondition(ConditionType_t type) +{ + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + updateMapCache(); + } + + updateIdleStatus(); +} + +void Monster::onThink(uint32_t interval) +{ + if (OTSYS_TIME() < earliestWakeUpTime) { + return; + } + + Creature::onThink(interval); + + if (mType->info.thinkEvent != -1) { + // onThink(self, interval) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Monster::onThink] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.thinkEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.thinkEvent); + + LuaScriptInterface::pushUserdata(L, this); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + lua_pushnumber(L, interval); + + if (scriptInterface->callFunction(2)) { + return; + } + } + + if (!isInSpawnRange(position) || (lifetime > 0 && (OTSYS_TIME() >= lifetime))) { + // Despawn creatures if they are out of their spawn zone + g_game.removeCreature(this); + g_game.addMagicEffect(getPosition(), CONST_ME_POFF); + } else { + updateIdleStatus(); + + if (!isIdle) { + addEventWalk(); + + if (isSummon()) { + if (!attackedCreature) { + if (getMaster() && getMaster()->getAttackedCreature()) { + selectTarget(getMaster()->getAttackedCreature()); + } else if (getMaster() != followCreature) { + //Our master has not ordered us to attack anything, lets follow him around instead. + setFollowCreature(getMaster()); + } + } else if (attackedCreature == this) { + setFollowCreature(nullptr); + } else if (followCreature != attackedCreature) { + setFollowCreature(attackedCreature); + } + + if (master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1 && !monster->hasFollowPath) { + setFollowCreature(master); + setAttackedCreature(nullptr); + } + } + } + } else if (!targetList.empty()) { + if (!followCreature || !hasFollowPath) { + searchTarget(TARGETSEARCH_ANY); + } else if (isFleeing()) { + if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { + searchTarget(TARGETSEARCH_NEAREST); + } + } + } + + onThinkTarget(interval); + onThinkYell(interval); + onThinkDefense(interval); + } + } +} + +void Monster::doAttacking(uint32_t) +{ + if (!attackedCreature || (isSummon() && attackedCreature == this)) { + return; + } + + const Position& myPos = getPosition(); + const Position& targetPos = attackedCreature->getPosition(); + + bool updateLook = false; + + if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { + updateLook = true; + if (Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED)) { + egibleToDance = true; + earliestAttackTime = OTSYS_TIME() + 2000; + removeCondition(CONDITION_AGGRESSIVE, true); + } + } + + for (spellBlock_t& spellBlock : mType->info.attackSpells) { + if (spellBlock.range != 0 && std::max(Position::getDistanceX(myPos, targetPos), Position::getDistanceY(myPos, targetPos)) <= spellBlock.range) { + if (normal_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { + updateLookDirection(); + + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; + + spellBlock.spell->castSpell(this, attackedCreature); + egibleToDance = true; + } + } + } + + if (updateLook) { + updateLookDirection(); + } +} + +bool Monster::canUseAttack(const Position& pos, const Creature* target) const +{ + if (isHostile()) { + const Position& targetPos = target->getPosition(); + uint32_t distance = std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)); + for (const spellBlock_t& spellBlock : mType->info.attackSpells) { + if (spellBlock.range != 0 && distance <= spellBlock.range) { + return g_game.isSightClear(pos, targetPos, true); + } + } + return false; + } + return true; +} + +void Monster::onThinkTarget(uint32_t interval) +{ + if (!isSummon()) { + if (mType->info.changeTargetSpeed != 0) { + bool canChangeTarget = true; + + if (targetChangeCooldown > 0) { + targetChangeCooldown -= interval; + + if (targetChangeCooldown <= 0) { + targetChangeCooldown = 0; + targetChangeTicks = mType->info.changeTargetSpeed; + } else { + canChangeTarget = false; + } + } + + if (canChangeTarget) { + targetChangeTicks += interval; + + if (targetChangeTicks >= mType->info.changeTargetSpeed) { + targetChangeTicks = 0; + + if (mType->info.changeTargetChance > uniform_random(0, 99)) { + // search target strategies, if no strategy succeeds, target is not switched + int32_t random = uniform_random(0, 99); + int32_t current_strategy = 0; + + TargetSearchType_t searchType = TARGETSEARCH_ANY; + + do + { + int32_t strategy = 0; + + if (current_strategy == 0) { + strategy = mType->info.strategyNearestEnemy; + searchType = TARGETSEARCH_NEAREST; + } else if (current_strategy == 1) { + strategy = mType->info.strategyWeakestEnemy; + searchType = TARGETSEARCH_WEAKEST; + } else if (current_strategy == 2) { + strategy = mType->info.strategyMostDamageEnemy; + searchType = TARGETSEARCH_MOSTDAMAGE; + } else if (current_strategy == 3) { + strategy = mType->info.strategyRandomEnemy; + searchType = TARGETSEARCH_RANDOM; + } + + if (random < strategy) { + break; + } + + current_strategy++; + random -= strategy; + } while (current_strategy <= 3); + + if (searchType != TARGETSEARCH_ANY) { + searchTarget(searchType); + } + } + } + } + } + } +} + +void Monster::onThinkDefense(uint32_t) +{ + for (const spellBlock_t& spellBlock : mType->info.defenseSpells) { + if (normal_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { + minCombatValue = spellBlock.minCombatValue; + maxCombatValue = spellBlock.maxCombatValue; + spellBlock.spell->castSpell(this, this); + } + } + + if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) { + for (const summonBlock_t& summonBlock : mType->info.summons) { + if (summons.size() >= mType->info.maxSummons) { + continue; + } + + uint32_t summonCount = 0; + for (Creature* summon : summons) { + if (summon->getName() == summonBlock.name) { + ++summonCount; + } + } + + if (summonCount >= summonBlock.max) { + continue; + } + + if (normal_random(0, summonBlock.chance) == 0 && (health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { + Monster* summon = Monster::createMonster(summonBlock.name); + if (summon) { + const Position& summonPos = getPosition(); + + addSummon(summon); + + if (!g_game.placeCreature(summon, summonPos, false, summonBlock.force)) { + removeSummon(summon); + } else { + g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); + } + } + } + } + } +} + +void Monster::onThinkYell(uint32_t) +{ + if (mType->info.voiceVector.empty()) { + return; + } + + int32_t randomResult = rand(); + if (rand() == 50 * (randomResult / 50)) { + int32_t totalVoices = mType->info.voiceVector.size(); + const voiceBlock_t& voice = mType->info.voiceVector[rand() % totalVoices + 1]; + + if (voice.yellText) { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, voice.text, false); + } else { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, voice.text, false); + } + } +} + +void Monster::onWalk() +{ + Creature::onWalk(); +} + +bool Monster::pushItem(Item* item) +{ + const Position& centerPos = item->getPosition(); + + static std::vector> relList { + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }; + + std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); + + for (const auto& it : relList) { + Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); + Tile* tile = g_game.map.getTile(tryPos); + if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) { + if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr) == RETURNVALUE_NOERROR) { + return true; + } + } + } + return false; +} + +void Monster::pushItems(Tile* tile) +{ + //We can not use iterators here since we can push the item to another tile + //which will invalidate the iterator. + //start from the end to minimize the amount of traffic + if (TileItemVector* items = tile->getItemList()) { + uint32_t moveCount = 0; + uint32_t removeCount = 0; + + int32_t downItemSize = tile->getDownItemCount(); + for (int32_t i = downItemSize; --i >= 0;) { + Item* item = items->at(i); + if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) + || item->hasProperty(CONST_PROP_BLOCKSOLID))) { + if (moveCount < 20 && Monster::pushItem(item)) { + ++moveCount; + } else if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { + ++removeCount; + } + } + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF); + } + } +} + +bool Monster::pushCreature(Creature* creature) +{ + static std::vector dirList { + DIRECTION_NORTH, + DIRECTION_WEST, DIRECTION_EAST, + DIRECTION_SOUTH + }; + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + + for (Direction dir : dirList) { + const Position& tryPos = Spells::getCasterPosition(creature, dir); + Tile* toTile = g_game.map.getTile(tryPos); + if (toTile && !toTile->hasFlag(TILESTATE_BLOCKPATH)) { + if (g_game.internalMoveCreature(creature, dir) == RETURNVALUE_NOERROR) { + return true; + } + } + } + return false; +} + +void Monster::pushCreatures(Tile* tile) +{ + //We can not use iterators here since we can push a creature to another tile + //which will invalidate the iterator. + if (CreatureVector* creatures = tile->getCreatures()) { + uint32_t removeCount = 0; + Monster* lastPushedMonster = nullptr; + + for (size_t i = 0; i < creatures->size();) { + Monster* monster = creatures->at(i)->getMonster(); + if (monster && monster->isPushable()) { + if (monster != lastPushedMonster && Monster::pushCreature(monster)) { + lastPushedMonster = monster; + continue; + } + + monster->changeHealth(-monster->getHealth()); + monster->setDropLoot(false); + removeCount++; + } + + ++i; + } + + if (removeCount > 0) { + g_game.addMagicEffect(tile->getPosition(), CONST_ME_BLOCKHIT); + } + } +} + +bool Monster::getNextStep(Direction& direction, uint32_t& flags) +{ + if (isIdle || getHealth() <= 0) { + //we dont have anyone watching might aswell stop walking + eventWalk = 0; + return false; + } + + bool result = false; + if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { + if (OTSYS_TIME() >= nextDanceStepRound) { + updateLookDirection(); + nextDanceStepRound = OTSYS_TIME() + 200 + getStepDuration(); + + //choose a random direction + result = getRandomStep(getPosition(), direction); + } + } else if ((isSummon() && isMasterInRange) || followCreature) { + result = Creature::getNextStep(direction, flags); + if (result) { + flags |= FLAG_PATHFINDING; + } else { + //target dancing + if (attackedCreature && attackedCreature == followCreature) { + if (isFleeing()) { + result = getDanceStep(getPosition(), direction, false, false); + } else if (egibleToDance && OTSYS_TIME() >= earliestDanceTime) { + if (mType->info.targetDistance >= 4) { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::getDistanceX(myPos, targetPos) == 4 || Position::getDistanceY(myPos, targetPos) == 4) { + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + if (danceRandomResult <= 3 && canWalkTo(myPos, direction)) { + int32_t xTest = targetPos.x - currentX; + if (currentX - targetPos.x > -1) { + xTest = currentX - targetPos.x; + } + + int32_t yTest = targetPos.y - currentY; + if (currentY - targetPos.y > -1) { + yTest = currentY - targetPos.y; + } + + int32_t realTest = yTest; + + if (xTest >= yTest) { + realTest = xTest; + } + + if (realTest == 4) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } + } else { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::areInRange<1, 1>(myPos, targetPos)) { + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + Position position = myPos; + position.x = currentX; + position.y = currentY; + + if (danceRandomResult <= 3 && + canWalkTo(myPos, direction) && + Position::areInRange<1, 1>(position, targetPos)) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } + } + } + } + } + + if (result && (canPushItems() || canPushCreatures())) { + const Position& pos = Spells::getCasterPosition(this, direction); + Tile* tile = g_game.map.getTile(pos); + if (tile) { + if (canPushItems()) { + Monster::pushItems(tile); + } + + if (canPushCreatures()) { + Monster::pushCreatures(tile); + } + } + } + + return result; +} + +bool Monster::getRandomStep(const Position& creaturePos, Direction& direction) const +{ + static std::vector dirList{ + DIRECTION_NORTH, + DIRECTION_WEST, DIRECTION_EAST, + DIRECTION_SOUTH + }; + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + + for (Direction dir : dirList) { + if (canWalkTo(creaturePos, dir)) { + direction = dir; + return true; + } + } + return false; +} + +bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, + bool keepAttack /*= true*/, bool keepDistance /*= true*/) +{ + bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); + + assert(attackedCreature != nullptr); + const Position& centerPos = attackedCreature->getPosition(); + + int_fast32_t offset_x = Position::getOffsetX(creaturePos, centerPos); + int_fast32_t offset_y = Position::getOffsetY(creaturePos, centerPos); + + int_fast32_t distance_x = std::abs(offset_x); + int_fast32_t distance_y = std::abs(offset_y); + + uint32_t centerToDist = std::max(distance_x, distance_y); + + std::vector dirList; + + if (!keepDistance || offset_y >= 0) { + uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY())); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_NORTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_NORTH); + } + } + } + + if (!keepDistance || offset_y <= 0) { + uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() + 1) - centerPos.getY())); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_SOUTH)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_SOUTH); + } + } + } + + if (!keepDistance || offset_x <= 0) { + uint32_t tmpDist = std::max(std::abs((creaturePos.getX() + 1) - centerPos.getX()), distance_y); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_EAST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_EAST); + } + } + } + + if (!keepDistance || offset_x >= 0) { + uint32_t tmpDist = std::max(std::abs((creaturePos.getX() - 1) - centerPos.getX()), distance_y); + if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_WEST)) { + bool result = true; + + if (keepAttack) { + result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); + } + + if (result) { + dirList.push_back(DIRECTION_WEST); + } + } + } + + if (!dirList.empty()) { + std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); + direction = dirList[uniform_random(0, dirList.size() - 1)]; + return true; + } + return false; +} + +bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, bool flee /* = false */) +{ + const Position& creaturePos = getPosition(); + + int_fast32_t dx = Position::getDistanceX(creaturePos, targetPos); + int_fast32_t dy = Position::getDistanceY(creaturePos, targetPos); + + int32_t distance = std::max(dx, dy); + + if (!flee && (distance > mType->info.targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) { + return false; // let the A* calculate it + } else if (!flee && distance == mType->info.targetDistance) { + return true; // we don't really care here, since it's what we wanted to reach (a dancestep will take of dancing in that position) + } + + int_fast32_t offsetx = Position::getOffsetX(creaturePos, targetPos); + int_fast32_t offsety = Position::getOffsetY(creaturePos, targetPos); + + if (dx <= 1 && dy <= 1) { + //seems like a target is near, it this case we need to slow down our movements (as a monster) + if (stepDuration < 2) { + stepDuration++; + } + } else if (stepDuration > 0) { + stepDuration--; + } + + if (offsetx == 0 && offsety == 0) { + return getRandomStep(creaturePos, direction); // player is "on" the monster so let's get some random step and rest will be taken care later. + } + + if (dx == dy) { + //player is diagonal to the monster + if (offsetx >= 1 && offsety >= 1) { + // player is NW + //escape to SE, S or E [and some extra] + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (s && e) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } else if (canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_SOUTHEAST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + + if (flee) { + if (n && w) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_WEST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_WEST; + } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_NORTH; + } + + return true; + } else if (offsetx <= -1 && offsety <= -1) { + //player is SE + //escape to NW , W or N [and some extra] + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + + if (w && n) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } + + if (canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_NORTHWEST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (flee) { + if (s && e) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + if (s && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_SOUTH; + } else if (e && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_EAST; + } + + return true; + } else if (offsetx >= 1 && offsety <= -1) { + //player is SW + //escape to NE, N, E [and some extra] + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (n && e) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + + if (canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { + direction = DIRECTION_NORTHEAST; + return true; + } + + /* fleeing */ + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + + if (flee) { + if (s && w) { + direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_WEST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } + } + + /* end of fleeing */ + + if (w && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_WEST; + } else if (s && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_SOUTH; + } + + return true; + } else if (offsetx <= -1 && offsety >= 1) { + // player is NE + //escape to SW, S, W [and some extra] + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (w && s) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_SOUTH; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } else if (canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { + direction = DIRECTION_SOUTHWEST; + return true; + } + + /* fleeing */ + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + + if (flee) { + if (n && e) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + if (e && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { + direction = DIRECTION_EAST; + } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { + direction = DIRECTION_NORTH; + } + + return true; + } + } + + //Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. + if (dy > dx) { + Direction playerDir = offsety < 0 ? DIRECTION_SOUTH : DIRECTION_NORTH; + switch (playerDir) { + case DIRECTION_NORTH: { + // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose WEST or EAST and again if we can't we need to decide about some diagonal movements. + if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { + direction = DIRECTION_SOUTH; + return true; + } + + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (w && e && offsetx == 0) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w && offsetx <= 0) { + direction = DIRECTION_WEST; + return true; + } else if (e && offsetx >= 0) { + direction = DIRECTION_EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); + bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); + if (sw || se) { + // we can move both dirs + if (sw && se) { + direction = boolean_random() ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST; + } else if (w) { + direction = DIRECTION_WEST; + } else if (sw) { + direction = DIRECTION_SOUTHWEST; + } else if (e) { + direction = DIRECTION_EAST; + } else if (se) { + direction = DIRECTION_SOUTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_NORTH)) { + // towards player, yea + direction = DIRECTION_NORTH; + return true; + } + + /* end of fleeing */ + break; + } + + case DIRECTION_SOUTH: { + if (canWalkTo(creaturePos, DIRECTION_NORTH)) { + direction = DIRECTION_NORTH; + return true; + } + + bool w = canWalkTo(creaturePos, DIRECTION_WEST); + bool e = canWalkTo(creaturePos, DIRECTION_EAST); + if (w && e && offsetx == 0) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w && offsetx <= 0) { + direction = DIRECTION_WEST; + return true; + } else if (e && offsetx >= 0) { + direction = DIRECTION_EAST; + return true; + } + + /* fleeing */ + if (flee) { + if (w && e) { + direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; + return true; + } else if (w) { + direction = DIRECTION_WEST; + return true; + } else if (e) { + direction = DIRECTION_EAST; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); + bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); + if (nw || ne) { + // we can move both dirs + if (nw && ne) { + direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST; + } else if (w) { + direction = DIRECTION_WEST; + } else if (nw) { + direction = DIRECTION_NORTHWEST; + } else if (e) { + direction = DIRECTION_EAST; + } else if (ne) { + direction = DIRECTION_NORTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_SOUTH)) { + // towards player, yea + direction = DIRECTION_SOUTH; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } else { + Direction playerDir = offsetx < 0 ? DIRECTION_EAST : DIRECTION_WEST; + switch (playerDir) { + case DIRECTION_WEST: { + if (canWalkTo(creaturePos, DIRECTION_EAST)) { + direction = DIRECTION_EAST; + return true; + } + + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (n && s && offsety == 0) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n && offsety <= 0) { + direction = DIRECTION_NORTH; + return true; + } else if (s && offsety >= 0) { + direction = DIRECTION_SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); + bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); + if (se || ne) { + if (se && ne) { + direction = boolean_random() ? DIRECTION_SOUTHEAST : DIRECTION_NORTHEAST; + } else if (s) { + direction = DIRECTION_SOUTH; + } else if (se) { + direction = DIRECTION_SOUTHEAST; + } else if (n) { + direction = DIRECTION_NORTH; + } else if (ne) { + direction = DIRECTION_NORTHEAST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_WEST)) { + // towards player, yea + direction = DIRECTION_WEST; + return true; + } + + /* end of fleeing */ + break; + } + + case DIRECTION_EAST: { + if (canWalkTo(creaturePos, DIRECTION_WEST)) { + direction = DIRECTION_WEST; + return true; + } + + bool n = canWalkTo(creaturePos, DIRECTION_NORTH); + bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); + if (n && s && offsety == 0) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n && offsety <= 0) { + direction = DIRECTION_NORTH; + return true; + } else if (s && offsety >= 0) { + direction = DIRECTION_SOUTH; + return true; + } + + /* fleeing */ + if (flee) { + if (n && s) { + direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; + return true; + } else if (n) { + direction = DIRECTION_NORTH; + return true; + } else if (s) { + direction = DIRECTION_SOUTH; + return true; + } + } + + /* end of fleeing */ + + bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); + bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); + if (nw || sw) { + if (nw && sw) { + direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_SOUTHWEST; + } else if (n) { + direction = DIRECTION_NORTH; + } else if (nw) { + direction = DIRECTION_NORTHWEST; + } else if (s) { + direction = DIRECTION_SOUTH; + } else if (sw) { + direction = DIRECTION_SOUTHWEST; + } + return true; + } + + /* fleeing */ + if (flee && canWalkTo(creaturePos, DIRECTION_EAST)) { + // towards player, yea + direction = DIRECTION_EAST; + return true; + } + + /* end of fleeing */ + break; + } + + default: + break; + } + } + + return true; +} + +bool Monster::canWalkTo(Position pos, Direction direction) const +{ + pos = getNextPosition(direction, pos); + if (isInSpawnRange(pos)) { + if (getWalkCache(pos) == 0) { + return false; + } + + Tile* tile = g_game.map.getTile(pos); + if (tile && tile->getTopVisibleCreature(this) == nullptr && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) { + return true; + } + } + return false; +} + +void Monster::death(Creature*) +{ + setAttackedCreature(nullptr); + + for (Creature* summon : summons) { + summon->changeHealth(-summon->getHealth()); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + summons.clear(); + + clearTargetList(); + clearFriendList(); + onIdleStatus(); +} + +Item* Monster::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) +{ + Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + if (mostDamageCreature) { + if (mostDamageCreature->getPlayer()) { + corpse->setCorpseOwner(mostDamageCreature->getID()); + } else { + const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); + if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) { + corpse->setCorpseOwner(mostDamageCreatureMaster->getID()); + } + } + } + } + return corpse; +} + +bool Monster::isInSpawnRange(const Position& pos) const +{ + if (!spawn) { + return true; + } + + if (Monster::despawnRadius == 0) { + return true; + } + + if (!Spawns::isInZone(masterPos, Monster::despawnRadius, pos)) { + return false; + } + + if (Monster::despawnRange == 0) { + return true; + } + + if (Position::getDistanceZ(pos, masterPos) > Monster::despawnRange) { + return false; + } + + return true; +} + +bool Monster::getCombatValues(int32_t& min, int32_t& max) +{ + if (minCombatValue == 0 && maxCombatValue == 0) { + return false; + } + + min = minCombatValue; + max = maxCombatValue; + return true; +} + +void Monster::updateLookDirection() +{ + Direction newDir = getDirection(); + + if (attackedCreature) { + const Position& pos = getPosition(); + const Position& attackedCreaturePos = attackedCreature->getPosition(); + int_fast32_t offsetx = Position::getOffsetX(attackedCreaturePos, pos); + int_fast32_t offsety = Position::getOffsetY(attackedCreaturePos, pos); + + int32_t dx = std::abs(offsetx); + int32_t dy = std::abs(offsety); + if (dx > dy) { + //look EAST/WEST + if (offsetx < 0) { + newDir = DIRECTION_WEST; + } else { + newDir = DIRECTION_EAST; + } + } else if (dx < dy) { + //look NORTH/SOUTH + if (offsety < 0) { + newDir = DIRECTION_NORTH; + } else { + newDir = DIRECTION_SOUTH; + } + } else { + Direction dir = getDirection(); + if (offsetx < 0 && offsety < 0) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_WEST; + } else if (dir == DIRECTION_EAST) { + newDir = DIRECTION_NORTH; + } + } else if (offsetx < 0 && offsety > 0) { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_WEST; + } else if (dir == DIRECTION_EAST) { + newDir = DIRECTION_SOUTH; + } + } else if (offsetx > 0 && offsety < 0) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_EAST; + } else if (dir == DIRECTION_WEST) { + newDir = DIRECTION_NORTH; + } + } else { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_EAST; + } else if (dir == DIRECTION_WEST) { + newDir = DIRECTION_SOUTH; + } + } + } + } + + if (direction != newDir) { + g_game.internalCreatureTurn(this, newDir); + } +} + +void Monster::dropLoot(Container* corpse, Creature*) +{ + if (corpse && lootDrop) { + mType->createLoot(corpse); + } +} + +void Monster::setNormalCreatureLight() +{ + internalLight = mType->info.light; +} + +void Monster::drainHealth(Creature* attacker, int32_t damage) +{ + Creature::drainHealth(attacker, damage); + if (isInvisible()) { + removeCondition(CONDITION_INVISIBLE); + } +} + +void Monster::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + //In case a player with ignore flag set attacks the monster + setIdle(false); + Creature::changeHealth(healthChange, sendHealthChange); +} + +bool Monster::challengeCreature(Creature* creature) +{ + if (isSummon()) { + return false; + } + + bool result = selectTarget(creature); + if (result) { + targetChangeCooldown = 1000; + targetChangeTicks = 0; + } + return result; +} + +bool Monster::convinceCreature(Creature* creature) +{ + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (!mType->info.isConvinceable) { + return false; + } + } + + if (isSummon()) { + if (getMaster() == creature) { + return false; + } + + Creature* oldMaster = getMaster(); + oldMaster->removeSummon(this); + } + + creature->addSummon(this); + + setFollowCreature(nullptr); + setAttackedCreature(nullptr); + + //destroy summons + for (Creature* summon : summons) { + summon->changeHealth(-summon->getHealth()); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + summons.clear(); + + isMasterInRange = true; + updateTargetList(); + updateIdleStatus(); + + //Notify surrounding about the change + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + g_game.map.getSpectators(list, creature->getPosition(), true); + for (Creature* spectator : list) { + spectator->onCreatureConvinced(creature, this); + } + + if (spawn) { + spawn->removeMonster(this); + spawn = nullptr; + } + return true; +} + +void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) +{ + if (convincer != this && (isFriend(creature) || isOpponent(creature))) { + updateTargetList(); + updateIdleStatus(); + } +} + +void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + + fpp.minTargetDist = 1; + fpp.maxTargetDist = mType->info.targetDistance; + + if (isSummon()) { + if (getMaster() == creature) { + fpp.maxTargetDist = 2; + fpp.fullPathSearch = true; + } else if (mType->info.targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } + } else if (isFleeing()) { + //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) + fpp.maxTargetDist = Map::maxViewportX; + fpp.clearSight = false; + fpp.keepDistance = true; + fpp.fullPathSearch = false; + } else if (mType->info.targetDistance <= 1) { + fpp.fullPathSearch = true; + } else { + fpp.fullPathSearch = !canUseAttack(getPosition(), creature); + } +} diff --git a/src/monster.h b/src/monster.h new file mode 100644 index 0000000..0e04f9e --- /dev/null +++ b/src/monster.h @@ -0,0 +1,274 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 +#define FS_MONSTER_H_9F5EEFE64314418CA7DA41D1B9409DD0 + +#include "tile.h" +#include "monsters.h" + +class Creature; +class Game; +class Spawn; +class Combat; + +typedef std::unordered_set CreatureHashSet; +typedef std::list CreatureList; + +enum TargetSearchType_t { + TARGETSEARCH_ANY, + TARGETSEARCH_RANDOM, + TARGETSEARCH_NEAREST, + TARGETSEARCH_WEAKEST, + TARGETSEARCH_MOSTDAMAGE, +}; + +class Monster final : public Creature +{ + public: + static Monster* createMonster(const std::string& name); + static int32_t despawnRange; + static int32_t despawnRadius; + + explicit Monster(MonsterType* mtype); + ~Monster(); + + // non-copyable + Monster(const Monster&) = delete; + Monster& operator=(const Monster&) = delete; + + Monster* getMonster() final { + return this; + } + const Monster* getMonster() const final { + return this; + } + + void setID() final { + if (id == 0) { + id = monsterAutoID++; + } + } + + void removeList() final; + void addList() final; + + const std::string& getName() const final { + return mType->name; + } + const std::string& getNameDescription() const final { + return mType->nameDescription; + } + std::string getDescription(int32_t) const final { + return strDescription + '.'; + } + + const Position& getMasterPos() const { + return masterPos; + } + void setMasterPos(Position pos) { + masterPos = pos; + } + + RaceType_t getRace() const final { + return mType->info.race; + } + int32_t getArmor() const final { + int32_t armor = mType->info.armor; + if (armor > 1) { + return rand() % (mType->info.armor >> 1) + (mType->info.armor >> 1); + } + + return armor; + } + int32_t getDefense() final; + bool isPushable() const final { + return mType->info.pushable && baseSpeed != 0; + } + bool isAttackable() const final { + return mType->info.isAttackable; + } + + bool canPushItems() const { + return mType->info.canPushItems; + } + bool canPushCreatures() const { + return mType->info.canPushCreatures; + } + bool isHostile() const { + return mType->info.isHostile; + } + bool canSee(const Position& pos) const final; + bool canSeeInvisibility() const final { + return isImmune(CONDITION_INVISIBLE); + } + uint32_t getManaCost() const { + return mType->info.manaCost; + } + void setSpawn(Spawn* spawn) { + this->spawn = spawn; + } + + void onAttackedCreature(Creature* creature) final; + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) final; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final; + + void drainHealth(Creature* attacker, int32_t damage) final; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; + void onWalk() final; + bool getNextStep(Direction& direction, uint32_t& flags) final; + void onFollowCreatureComplete(const Creature* creature) final; + + void onThink(uint32_t interval) final; + + bool challengeCreature(Creature* creature) final; + bool convinceCreature(Creature* creature) final; + + void setNormalCreatureLight() final; + bool getCombatValues(int32_t& min, int32_t& max) final; + + void doAttacking(uint32_t interval) final; + + bool searchTarget(TargetSearchType_t searchType); + bool selectTarget(Creature* creature); + + const CreatureList& getTargetList() const { + return targetList; + } + const CreatureHashSet& getFriendList() const { + return friendList; + } + + bool isTarget(const Creature* creature) const; + bool isFleeing() const { + return !isSummon() && getHealth() <= mType->info.runAwayHealth; + } + + bool getDistanceStep(const Position& targetPos, Direction& direction, bool flee = false); + bool isTargetNearby() const { + return stepDuration >= 1; + } + + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false); + + static uint32_t monsterAutoID; + + private: + CreatureHashSet friendList; + CreatureList targetList; + + std::string strDescription; + + MonsterType* mType; + Spawn* spawn = nullptr; + + int64_t lifetime = 0; + int64_t nextDanceStepRound = 0; + int64_t earliestAttackTime = 0; + int64_t earliestWakeUpTime = 0; + int64_t earliestDanceTime = 0; + + uint32_t targetChangeTicks = 0; + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t targetChangeCooldown = 0; + int32_t stepDuration = 0; + + Position masterPos; + + bool isIdle = true; + bool isMasterInRange = false; + bool egibleToDance = true; + + void onCreatureEnter(Creature* creature); + void onCreatureLeave(Creature* creature); + void onCreatureFound(Creature* creature, bool pushFront = false); + + void updateLookDirection(); + + void addFriend(Creature* creature); + void removeFriend(Creature* creature); + void addTarget(Creature* creature, bool pushFront = false); + void removeTarget(Creature* creature); + + void updateTargetList(); + void clearTargetList(); + void clearFriendList(); + + void death(Creature* lastHitCreature) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + + void setIdle(bool idle); + void updateIdleStatus(); + bool getIdleStatus() const { + return isIdle; + } + + void onAddCondition(ConditionType_t type) final; + void onEndCondition(ConditionType_t type) final; + void onCreatureConvinced(const Creature* convincer, const Creature* creature) final; + + bool canUseAttack(const Position& pos, const Creature* target) const; + bool getRandomStep(const Position& creaturePos, Direction& direction) const; + bool getDanceStep(const Position& creaturePos, Direction& direction, + bool keepAttack = true, bool keepDistance = true); + bool isInSpawnRange(const Position& pos) const; + bool canWalkTo(Position pos, Direction direction) const; + + static bool pushItem(Item* item); + static void pushItems(Tile* tile); + static bool pushCreature(Creature* creature); + static void pushCreatures(Tile* tile); + + void onThinkTarget(uint32_t interval); + void onThinkYell(uint32_t interval); + void onThinkDefense(uint32_t interval); + + bool isFriend(const Creature* creature) const; + bool isOpponent(const Creature* creature) const; + + uint64_t getLostExperience() const final { + return skillLoss ? mType->info.experience : 0; + } + uint16_t getLookCorpse() const final { + return mType->info.lookcorpse; + } + void dropLoot(Container* corpse, Creature* lastHitCreature) final; + uint32_t getDamageImmunities() const final { + return mType->info.damageImmunities; + } + uint32_t getConditionImmunities() const final { + return mType->info.conditionImmunities; + } + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; + bool useCacheMap() const final { + return true; + } + + friend class LuaScriptInterface; + friend class AreaSpawnEvent; + friend class Combat; + friend class Creature; +}; + +#endif diff --git a/src/monsters.cpp b/src/monsters.cpp new file mode 100644 index 0000000..08bf423 --- /dev/null +++ b/src/monsters.cpp @@ -0,0 +1,1070 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "monsters.h" +#include "monster.h" +#include "spells.h" +#include "combat.h" +#include "configmanager.h" +#include "game.h" + +#include "pugicast.h" + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern ConfigManager g_config; + +spellBlock_t::~spellBlock_t() +{ + if (combatSpell) { + delete spell; + } +} + +uint32_t Monsters::getLootRandom() +{ + return uniform_random(0, MAX_LOOTCHANCE); +} + +void MonsterType::createLoot(Container* corpse) +{ + if (g_config.getNumber(ConfigManager::RATE_LOOT) == 0) { + corpse->startDecaying(); + return; + } + + Item* bagItem = Item::CreateItem(2853, 1); + if (!bagItem) { + return; + } + + Container* bagContainer = bagItem->getContainer(); + if (!bagContainer) { + return; + } + + if (g_game.internalAddItem(corpse, bagItem) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(bagItem); + } + + bool includeBagLoot = false; + for (auto it = info.lootItems.rbegin(), end = info.lootItems.rend(); it != end; ++it) { + std::vector itemList = createLootItem(*it); + if (itemList.empty()) { + continue; + } + + for (Item* item : itemList) { + //check containers + if (Container* container = item->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + continue; + } + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.weaponType != WEAPON_NONE || + itemType.stopTime || + itemType.decayTime) { + includeBagLoot = true; + if (g_game.internalAddItem(bagContainer, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } + } else { + if (g_game.internalAddItem(corpse, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } + } + } + } + + if (!includeBagLoot) { + g_game.internalRemoveItem(bagItem); + } + + if (g_config.getBoolean(ConfigManager::SHOW_MONSTER_LOOT)) { + Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); + if (owner) { + std::ostringstream ss; + ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); + + if (owner->getParty()) { + owner->getParty()->broadcastPartyLoot(ss.str()); + } else { + owner->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + } + } + } + + corpse->startDecaying(); +} + +std::vector MonsterType::createLootItem(const LootBlock& lootBlock) +{ + int32_t itemCount = 0; + + uint32_t randvalue = Monsters::getLootRandom(); + uint32_t extraMoney = g_config.getNumber(ConfigManager::MONEY_RATE); + uint32_t countMax = lootBlock.countmax + 1; + + if (randvalue < g_config.getNumber(ConfigManager::RATE_LOOT) * lootBlock.chance) { + if (Item::items[lootBlock.id].stackable) { + if (lootBlock.id == 3031) { + countMax *= extraMoney; + } + + itemCount = randvalue % countMax; + } else { + itemCount = 1; + } + } + + std::vector itemList; + while (itemCount > 0) { + uint16_t n = static_cast(std::min(itemCount, 100)); + Item* tmpItem = Item::CreateItem(lootBlock.id, n); + if (!tmpItem) { + break; + } + + itemCount -= n; + + if (lootBlock.subType != -1) { + tmpItem->setSubType(lootBlock.subType); + } + + if (lootBlock.actionId != -1) { + tmpItem->setActionId(lootBlock.actionId); + } + + if (!lootBlock.text.empty()) { + tmpItem->setText(lootBlock.text); + } + + itemList.push_back(tmpItem); + } + return itemList; +} + +bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) +{ + auto it = lootblock.childLoot.begin(), end = lootblock.childLoot.end(); + if (it == end) { + return true; + } + + for (; it != end && parent->size() < parent->capacity(); ++it) { + auto itemList = createLootItem(*it); + for (Item* tmpItem : itemList) { + if (Container* container = tmpItem->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + } else { + parent->internalAddThing(container); + } + } else { + parent->internalAddThing(tmpItem); + } + } + } + return !parent->empty(); +} + +bool Monsters::loadFromXml(bool reloading /*= false*/) +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml"); + if (!result) { + printXMLError("Error - Monsters::loadFromXml", "data/monster/monsters.xml", result); + return false; + } + + loaded = true; + + std::list> monsterScriptList; + for (auto monsterNode : doc.child("monsters").children()) { + loadMonster("data/monster/" + std::string(monsterNode.attribute("file").as_string()), monsterNode.attribute("name").as_string(), monsterScriptList, reloading); + } + + if (!monsterScriptList.empty()) { + if (!scriptInterface) { + scriptInterface.reset(new LuaScriptInterface("Monster Interface")); + scriptInterface->initState(); + } + + for (const auto& scriptEntry : monsterScriptList) { + MonsterType* mType = scriptEntry.first; + if (scriptInterface->loadFile("data/monster/scripts/" + scriptEntry.second) == 0) { + mType->info.scriptInterface = scriptInterface.get(); + mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); + mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); + mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); + mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); + mType->info.thinkEvent = scriptInterface->getEvent("onThink"); + } else { + std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << scriptEntry.second << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + } + } + } + return true; +} + +bool Monsters::reload() +{ + loaded = false; + + scriptInterface.reset(); + + return loadFromXml(true); +} + +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t cycle, int32_t count, int32_t max_count) +{ + ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); + + condition->setParam(CONDITION_PARAM_CYCLE, cycle); + condition->setParam(CONDITION_PARAM_COUNT, count); + condition->setParam(CONDITION_PARAM_MAX_COUNT, max_count); + return condition; +} + +bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description) +{ + std::string name; + std::string scriptName; + bool isScripted; + + pugi::xml_attribute attr; + if ((attr = node.attribute("script"))) { + scriptName = attr.as_string(); + isScripted = true; + } else if ((attr = node.attribute("name"))) { + name = attr.as_string(); + isScripted = false; + } else { + return false; + } + + if ((attr = node.attribute("chance"))) { + uint32_t chance = pugi::cast(attr.value()); + if (chance > 100) { + chance = 100; + } + + if (chance == 0) { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Spell chance is zero: " << name << std::endl; + } + + sb.chance = chance; + } + + if ((attr = node.attribute("range"))) { + uint32_t range = pugi::cast(attr.value()); + if (range > (Map::maxViewportX * 2)) { + range = Map::maxViewportX * 2; + } + sb.range = range; + } else { + sb.range = Map::maxClientViewportX; + } + + if ((attr = node.attribute("min"))) { + sb.minCombatValue = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("max"))) { + sb.maxCombatValue = pugi::cast(attr.value()); + + //normalize values + if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { + int32_t value = sb.maxCombatValue; + sb.maxCombatValue = sb.minCombatValue; + sb.minCombatValue = value; + } + } + + if (auto spell = g_spells->getSpellByName(name)) { + sb.spell = spell; + return true; + } + + CombatSpell* combatSpell = nullptr; + bool needTarget = false; + bool needDirection = false; + + if (isScripted) { + if ((attr = node.attribute("direction"))) { + needDirection = attr.as_bool(); + } + + if ((attr = node.attribute("target"))) { + needTarget = attr.as_bool(); + } + + std::unique_ptr combatSpellPtr(new CombatSpell(nullptr, needTarget, needDirection)); + if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + scriptName)) { + return false; + } + + if (!combatSpellPtr->loadScriptCombat()) { + return false; + } + + combatSpell = combatSpellPtr.release(); + combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + } else { + Combat* combat = new Combat; + if ((attr = node.attribute("length"))) { + int32_t length = pugi::cast(attr.value()); + if (length > 0) { + int32_t spread = 3; + + //need direction spell + if ((attr = node.attribute("spread"))) { + spread = std::max(0, pugi::cast(attr.value())); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(length, spread); + combat->setArea(area); + + needDirection = true; + } + } + + if ((attr = node.attribute("radius"))) { + int32_t radius = pugi::cast(attr.value()); + + //target spell + if ((attr = node.attribute("target"))) { + needTarget = attr.as_bool(); + } + + AreaCombat* area = new AreaCombat(); + area->setupArea(radius); + combat->setArea(area); + } + + std::string tmpName = asLowerCaseString(name); + + if (tmpName == "physical") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); + combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); + } else if (tmpName == "bleed") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + } else if (tmpName == "poison" || tmpName == "earth") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE); + } else if (tmpName == "fire") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE); + } else if (tmpName == "energy") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE); + } else if (tmpName == "lifedrain") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN); + } else if (tmpName == "manadrain") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_MANADRAIN); + } else if (tmpName == "healing") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HEALING); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } else if (tmpName == "speed") { + int32_t speedChange = 0; + int32_t variation = 0; + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("speedchange"))) { + speedChange = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("variation"))) { + variation = pugi::cast(attr.value()); + } + + ConditionType_t conditionType; + if (speedChange > 0) { + conditionType = CONDITION_HASTE; + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } else { + conditionType = CONDITION_PARALYZE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + condition->setVariation(variation); + condition->setSpeedDelta(speedChange); + combat->setCondition(condition); + } else if (tmpName == "outfit") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("monster"))) { + MonsterType* mType = g_monsters.getMonsterType(attr.as_string()); + if (mType) { + ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->setOutfit(mType->info.outfit); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if ((attr = node.attribute("item"))) { + Outfit_t outfit; + outfit.lookTypeEx = pugi::cast(attr.value()); + + ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->setOutfit(outfit); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } + } else if (tmpName == "invisible") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->setCondition(condition); + } else if (tmpName == "drunk") { + int32_t duration = 10000; + + if ((attr = node.attribute("duration"))) { + duration = pugi::cast(attr.value()); + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + combat->setCondition(condition); + } else if (tmpName == "firefield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); + } else if (tmpName == "poisonfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); + } else if (tmpName == "energyfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); + } else if (tmpName == "firecondition" || tmpName == "energycondition" || + tmpName == "earthcondition" || tmpName == "poisoncondition" || + tmpName == "icecondition" || tmpName == "freezecondition" || + tmpName == "physicalcondition") { + ConditionType_t conditionType = CONDITION_NONE; + + if (tmpName == "firecondition") { + conditionType = CONDITION_FIRE; + } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { + conditionType = CONDITION_POISON; + } else if (tmpName == "energycondition") { + conditionType = CONDITION_ENERGY; + } + + int32_t cycle = 0; + if ((attr = node.attribute("count"))) { + cycle = std::abs(pugi::cast(attr.value())); + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing count attribute" << std::endl; + delete combat; + return false; + } + + int32_t count = 0; + + if (conditionType == CONDITION_POISON) { + count = 3; + } else if (conditionType == CONDITION_FIRE) { + count = 8; + cycle /= 10; + } else if (conditionType == CONDITION_ENERGY) { + count = 10; + cycle /= 20; + } + + Condition* condition = getDamageCondition(conditionType, cycle, count, count); + combat->setCondition(condition); + } else if (tmpName == "strength") { + // + } else if (tmpName == "effect") { + // + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; + delete combat; + return false; + } + + combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + + combatSpell = new CombatSpell(combat, needTarget, needDirection); + + for (auto attributeNode : node.children()) { + if ((attr = attributeNode.attribute("key"))) { + const char* value = attr.value(); + if (strcasecmp(value, "shooteffect") == 0) { + if ((attr = attributeNode.attribute("value"))) { + ShootType_t shoot = getShootType(attr.as_string()); + if (shoot != CONST_ANI_NONE) { + combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); + } else { + std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown shootEffect: " << attr.as_string() << std::endl; + } + } + } else if (strcasecmp(value, "areaeffect") == 0) { + if ((attr = attributeNode.attribute("value"))) { + MagicEffectClasses effect = getMagicEffect(attr.as_string()); + if (effect != CONST_ME_NONE) { + combat->setParam(COMBAT_PARAM_EFFECT, effect); + } else { + std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown areaEffect: " << attr.as_string() << std::endl; + } + } + } else { + std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << attr.as_string() << "\" does not exist." << std::endl; + } + } + } + } + + sb.spell = combatSpell; + if (combatSpell) { + sb.combatSpell = true; + } + return true; +} + +bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading /*= false*/) +{ + MonsterType* mType = nullptr; + bool new_mType = true; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(file.c_str()); + if (!result) { + printXMLError("Error - Monsters::loadMonster", file, result); + return false; + } + + pugi::xml_node monsterNode = doc.child("monster"); + if (!monsterNode) { + std::cout << "[Error - Monsters::loadMonster] Missing monster node in: " << file << std::endl; + return false; + } + + pugi::xml_attribute attr; + if (!(attr = monsterNode.attribute("name"))) { + std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file << std::endl; + return false; + } + + if (reloading) { + mType = getMonsterType(monsterName); + if (mType != nullptr) { + new_mType = false; + mType->info = {}; + } + } + + if (new_mType) { + mType = &monsters[asLowerCaseString(monsterName)]; + } + + mType->name = attr.as_string(); + + if ((attr = monsterNode.attribute("nameDescription"))) { + mType->nameDescription = attr.as_string(); + } else { + mType->nameDescription = "a " + asLowerCaseString(mType->name); + } + + if ((attr = monsterNode.attribute("race"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + uint16_t tmpInt = pugi::cast(attr.value()); + if (tmpStrValue == "venom" || tmpInt == 1) { + mType->info.race = RACE_VENOM; + } else if (tmpStrValue == "blood" || tmpInt == 2) { + mType->info.race = RACE_BLOOD; + } else if (tmpStrValue == "undead" || tmpInt == 3) { + mType->info.race = RACE_UNDEAD; + } else if (tmpStrValue == "fire" || tmpInt == 4) { + mType->info.race = RACE_FIRE; + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; + } + } + + if ((attr = monsterNode.attribute("experience"))) { + mType->info.experience = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("speed"))) { + mType->info.baseSpeed = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("manacost"))) { + mType->info.manaCost = pugi::cast(attr.value()); + } + + if ((attr = monsterNode.attribute("skull"))) { + mType->info.skull = getSkullType(attr.as_string()); + } + + if ((attr = monsterNode.attribute("script"))) { + monsterScriptList.emplace_back(mType, attr.as_string()); + } + + pugi::xml_node node; + if ((node = monsterNode.child("health"))) { + if ((attr = node.attribute("now"))) { + mType->info.health = pugi::cast(attr.value()); + } else { + std::cout << "[Error - Monsters::loadMonster] Missing health now. " << file << std::endl; + } + + if ((attr = node.attribute("max"))) { + mType->info.healthMax = pugi::cast(attr.value()); + } else { + std::cout << "[Error - Monsters::loadMonster] Missing health max. " << file << std::endl; + } + } + + if ((node = monsterNode.child("flags"))) { + for (auto flagNode : node.children()) { + attr = flagNode.first_attribute(); + const char* attrName = attr.name(); + if (strcasecmp(attrName, "summonable") == 0) { + mType->info.isSummonable = attr.as_bool(); + } else if (strcasecmp(attrName, "attackable") == 0) { + mType->info.isAttackable = attr.as_bool(); + } else if (strcasecmp(attrName, "hostile") == 0) { + mType->info.isHostile = attr.as_bool(); + } else if (strcasecmp(attrName, "illusionable") == 0) { + mType->info.isIllusionable = attr.as_bool(); + } else if (strcasecmp(attrName, "convinceable") == 0) { + mType->info.isConvinceable = attr.as_bool(); + } else if (strcasecmp(attrName, "pushable") == 0) { + mType->info.pushable = attr.as_bool(); + } else if (strcasecmp(attrName, "canpushitems") == 0) { + mType->info.canPushItems = attr.as_bool(); + } else if (strcasecmp(attrName, "canpushcreatures") == 0) { + mType->info.canPushCreatures = attr.as_bool(); + } else if (strcasecmp(attrName, "lightlevel") == 0) { + mType->info.light.level = pugi::cast(attr.value()); + } else if (strcasecmp(attrName, "lightcolor") == 0) { + mType->info.light.color = pugi::cast(attr.value()); + } else if (strcasecmp(attrName, "targetdistance") == 0) { + mType->info.targetDistance = std::max(1, pugi::cast(attr.value())); + } else if (strcasecmp(attrName, "runonhealth") == 0) { + mType->info.runAwayHealth = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; + } + } + + //if a monster can push creatures, + // it should not be pushable + if (mType->info.canPushCreatures) { + mType->info.pushable = false; + } + } + + if ((node = monsterNode.child("targetchange"))) { + if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { + mType->info.changeTargetSpeed = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing targetchange speed. " << file << std::endl; + } + + if ((attr = node.attribute("chance"))) { + mType->info.changeTargetChance = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing targetchange chance. " << file << std::endl; + } + } + + if ((node = monsterNode.child("targetstrategy"))) { + if ((attr = node.attribute("nearest"))) { + mType->info.strategyNearestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing nearest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("weakest"))) { + mType->info.strategyWeakestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing weakest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("mostdamage"))) { + mType->info.strategyMostDamageEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing most damage enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("random"))) { + mType->info.strategyRandomEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing random enemy chance. " << file << std::endl; + } + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing target change strategies. " << file << std::endl; + } + + if ((node = monsterNode.child("look"))) { + if ((attr = node.attribute("type"))) { + mType->info.outfit.lookType = pugi::cast(attr.value()); + + if ((attr = node.attribute("head"))) { + mType->info.outfit.lookHead = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("body"))) { + mType->info.outfit.lookBody = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("legs"))) { + mType->info.outfit.lookLegs = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("feet"))) { + mType->info.outfit.lookFeet = pugi::cast(attr.value()); + } + } else if ((attr = node.attribute("typeex"))) { + mType->info.outfit.lookTypeEx = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl; + } + + if ((attr = node.attribute("corpse"))) { + mType->info.lookcorpse = pugi::cast(attr.value()); + } + } + + if ((node = monsterNode.child("attacks"))) { + if ((attr = node.attribute("attack"))) { + mType->info.attack = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("skill"))) { + mType->info.skill = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("poison"))) { + mType->info.poison = pugi::cast(attr.value()); + } + + for (auto attackNode : node.children()) { + spellBlock_t sb; + if (deserializeSpell(attackNode, sb, monsterName)) { + mType->info.attackSpells.emplace_back(std::move(sb)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("defenses"))) { + if ((attr = node.attribute("defense"))) { + mType->info.defense = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("armor"))) { + mType->info.armor = pugi::cast(attr.value()); + } + + for (auto defenseNode : node.children()) { + spellBlock_t sb; + if (deserializeSpell(defenseNode, sb, monsterName)) { + mType->info.defenseSpells.emplace_back(std::move(sb)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("immunities"))) { + for (auto immunityNode : node.children()) { + if ((attr = immunityNode.attribute("name"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + if (tmpStrValue == "physical") { + mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + } else if (tmpStrValue == "energy") { + mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->info.conditionImmunities |= CONDITION_ENERGY; + } else if (tmpStrValue == "fire") { + mType->info.damageImmunities |= COMBAT_FIREDAMAGE; + mType->info.conditionImmunities |= CONDITION_FIRE; + } else if (tmpStrValue == "poison" || + tmpStrValue == "earth") { + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; + } else if (tmpStrValue == "lifedrain") { + mType->info.damageImmunities |= COMBAT_LIFEDRAIN; + } else if (tmpStrValue == "manadrain") { + mType->info.damageImmunities |= COMBAT_MANADRAIN; + } else if (tmpStrValue == "paralyze") { + mType->info.conditionImmunities |= CONDITION_PARALYZE; + } else if (tmpStrValue == "outfit") { + mType->info.conditionImmunities |= CONDITION_OUTFIT; + } else if (tmpStrValue == "drunk") { + mType->info.conditionImmunities |= CONDITION_DRUNK; + } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { + mType->info.conditionImmunities |= CONDITION_INVISIBLE; + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; + } + } else if ((attr = immunityNode.attribute("physical"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + } + } else if ((attr = immunityNode.attribute("energy"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; + mType->info.conditionImmunities |= CONDITION_ENERGY; + } + } else if ((attr = immunityNode.attribute("fire"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_FIREDAMAGE; + mType->info.conditionImmunities |= CONDITION_FIRE; + } + } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; + } + } else if ((attr = immunityNode.attribute("lifedrain"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_LIFEDRAIN; + } + } else if ((attr = immunityNode.attribute("manadrain"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_MANADRAIN; + } + } else if ((attr = immunityNode.attribute("paralyze"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_PARALYZE; + } + } else if ((attr = immunityNode.attribute("outfit"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_OUTFIT; + } + } else if ((attr = immunityNode.attribute("drunk"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_DRUNK; + } + } else if ((attr = immunityNode.attribute("invisible")) || (attr = immunityNode.attribute("invisibility"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_INVISIBLE; + } + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("voices"))) { + for (auto voiceNode : node.children()) { + voiceBlock_t vb; + if ((attr = voiceNode.attribute("sentence"))) { + vb.text = attr.as_string(); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing voice sentence. " << file << std::endl; + } + + if ((attr = voiceNode.attribute("yell"))) { + vb.yellText = attr.as_bool(); + } else { + vb.yellText = false; + } + mType->info.voiceVector.emplace_back(vb); + } + } + + if ((node = monsterNode.child("loot"))) { + for (auto lootNode : node.children()) { + LootBlock lootBlock; + if (loadLootItem(lootNode, lootBlock)) { + mType->info.lootItems.emplace_back(std::move(lootBlock)); + } else { + std::cout << "[Warning - Monsters::loadMonster] Cant load loot. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("elements"))) { + for (auto elementNode : node.children()) { + if ((attr = elementNode.attribute("physicalPercent"))) { + mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { + mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("firePercent"))) { + mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("energyPercent"))) { + mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("lifedrainPercent"))) { + mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("manadrainPercent"))) { + mType->info.elementMap[COMBAT_MANADRAIN] = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown element percent. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("summons"))) { + if ((attr = node.attribute("maxSummons"))) { + mType->info.maxSummons = std::min(pugi::cast(attr.value()), 100); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing summons maxSummons. " << file << std::endl; + } + + for (auto summonNode : node.children()) { + int32_t chance = 100; + int32_t max = mType->info.maxSummons; + bool force = false; + + if ((attr = summonNode.attribute("chance"))) { + chance = pugi::cast(attr.value()); + } + + if ((attr = summonNode.attribute("max"))) { + max = pugi::cast(attr.value()); + } + + if ((attr = summonNode.attribute("force"))) { + force = attr.as_bool(); + } + + if ((attr = summonNode.attribute("name"))) { + summonBlock_t sb; + sb.name = attr.as_string(); + sb.chance = chance; + sb.max = max; + sb.force = force; + mType->info.summons.emplace_back(sb); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing summon name. " << file << std::endl; + } + } + } + + if ((node = monsterNode.child("script"))) { + for (auto eventNode : node.children()) { + if ((attr = eventNode.attribute("name"))) { + mType->info.scripts.emplace_back(attr.as_string()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing name for script event. " << file << std::endl; + } + } + } + + mType->info.summons.shrink_to_fit(); + mType->info.lootItems.shrink_to_fit(); + mType->info.attackSpells.shrink_to_fit(); + mType->info.defenseSpells.shrink_to_fit(); + mType->info.voiceVector.shrink_to_fit(); + mType->info.scripts.shrink_to_fit(); + return true; +} + +bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) +{ + pugi::xml_attribute attr; + if ((attr = node.attribute("id"))) { + lootBlock.id = pugi::cast(attr.value()); + } else if ((attr = node.attribute("name"))) { + auto name = attr.as_string(); + auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name)); + + if (ids.first == Item::items.nameToItems.cend()) { + std::cout << "[Warning - Monsters::loadMonster] Unknown loot item \"" << name << "\". " << std::endl; + return false; + } + + uint32_t id = ids.first->second; + + if (std::next(ids.first) != ids.second) { + std::cout << "[Warning - Monsters::loadMonster] Non-unique loot item \"" << name << "\". " << std::endl; + return false; + } + + lootBlock.id = id; + } + + if (lootBlock.id == 0) { + return false; + } + + if ((attr = node.attribute("countmax"))) { + lootBlock.countmax = std::max(1, pugi::cast(attr.value())); + } else { + lootBlock.countmax = 1; + } + + if ((attr = node.attribute("chance")) || (attr = node.attribute("chance1"))) { + lootBlock.chance = std::min(MAX_LOOTCHANCE, pugi::cast(attr.value())); + } else { + lootBlock.chance = MAX_LOOTCHANCE; + } + + if (Item::items[lootBlock.id].isContainer()) { + loadLootContainer(node, lootBlock); + } + + //optional + if ((attr = node.attribute("subtype"))) { + lootBlock.subType = pugi::cast(attr.value()); + } else { + uint32_t charges = Item::items[lootBlock.id].charges; + if (charges != 0) { + lootBlock.subType = charges; + } + } + + if ((attr = node.attribute("actionId"))) { + lootBlock.actionId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("text"))) { + lootBlock.text = attr.as_string(); + } + return true; +} + +void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) +{ + for (auto subNode : node.children()) { + LootBlock lootBlock; + if (loadLootItem(subNode, lootBlock)) { + lBlock.childLoot.emplace_back(std::move(lootBlock)); + } + } +} + +MonsterType* Monsters::getMonsterType(const std::string& name) +{ + auto it = monsters.find(asLowerCaseString(name)); + + if (it == monsters.end()) { + return nullptr; + } + return &it->second; +} diff --git a/src/monsters.h b/src/monsters.h new file mode 100644 index 0000000..671ffe8 --- /dev/null +++ b/src/monsters.h @@ -0,0 +1,201 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D +#define FS_MONSTERS_H_776E8327BCE2450EB7C4A260785E6C0D + +#include "creature.h" + +#define MAX_LOOTCHANCE 1000 +#define MAX_STATICWALK 100 + +struct LootBlock { + uint16_t id; + uint32_t countmax; + uint32_t chance; + + //optional + int32_t subType; + int32_t actionId; + std::string text; + + std::vector childLoot; + LootBlock() { + id = 0; + countmax = 0; + chance = 0; + + subType = -1; + actionId = -1; + } +}; + +struct summonBlock_t { + std::string name; + uint32_t chance; + uint32_t max; + bool force = false; +}; + +class BaseSpell; +struct spellBlock_t { + constexpr spellBlock_t() = default; + ~spellBlock_t(); + spellBlock_t(const spellBlock_t& other) = delete; + spellBlock_t& operator=(const spellBlock_t& other) = delete; + spellBlock_t(spellBlock_t&& other) : + spell(other.spell), + chance(other.chance), + range(other.range), + minCombatValue(other.minCombatValue), + maxCombatValue(other.maxCombatValue), + attack(other.attack), + skill(other.skill), + combatSpell(other.combatSpell) { + other.spell = nullptr; + } + + BaseSpell* spell = nullptr; + uint32_t chance = 100; + uint32_t range = 0; + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t attack = 0; + int32_t skill = 0; + bool combatSpell = false; +}; + +struct voiceBlock_t { + std::string text; + bool yellText; +}; + +class MonsterType +{ + struct MonsterInfo { + LuaScriptInterface* scriptInterface; + + std::map elementMap; + + std::vector voiceVector; + + std::vector lootItems; + std::vector scripts; + std::vector attackSpells; + std::vector defenseSpells; + std::vector summons; + + Skulls_t skull = SKULL_NONE; + Outfit_t outfit = {}; + RaceType_t race = RACE_BLOOD; + + LightInfo light = {}; + uint16_t lookcorpse = 0; + + uint64_t experience = 0; + + uint32_t manaCost = 0; + uint32_t maxSummons = 0; + uint32_t changeTargetSpeed = 0; + uint32_t conditionImmunities = 0; + uint32_t damageImmunities = 0; + uint32_t baseSpeed = 70; + + int32_t creatureAppearEvent = -1; + int32_t creatureDisappearEvent = -1; + int32_t creatureMoveEvent = -1; + int32_t creatureSayEvent = -1; + int32_t thinkEvent = -1; + int32_t targetDistance = 1; + int32_t runAwayHealth = 0; + int32_t health = 100; + int32_t healthMax = 100; + int32_t changeTargetChance = 0; + int32_t strategyNearestEnemy = 0; + int32_t strategyWeakestEnemy = 0; + int32_t strategyMostDamageEnemy = 0; + int32_t strategyRandomEnemy = 0; + int32_t armor = 0; + int32_t defense = 0; + int32_t attack = 0; + int32_t skill = 0; + int32_t poison = 0; + + bool canPushItems = false; + bool canPushCreatures = false; + bool pushable = true; + bool isSummonable = false; + bool isIllusionable = false; + bool isConvinceable = false; + bool isAttackable = true; + bool isHostile = true; + bool hiddenHealth = false; + }; + + public: + MonsterType() = default; + + // non-copyable + MonsterType(const MonsterType&) = delete; + MonsterType& operator=(const MonsterType&) = delete; + + std::string name; + std::string nameDescription; + + MonsterInfo info; + + void createLoot(Container* corpse); + bool createLootContainer(Container* parent, const LootBlock& lootblock); + std::vector createLootItem(const LootBlock& lootBlock); +}; + +class Monsters +{ + public: + Monsters() = default; + // non-copyable + Monsters(const Monsters&) = delete; + Monsters& operator=(const Monsters&) = delete; + + bool loadFromXml(bool reloading = false); + bool isLoaded() const { + return loaded; + } + bool reload(); + + MonsterType* getMonsterType(const std::string& name); + + static uint32_t getLootRandom(); + + private: + ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t cycles, int32_t count, int32_t max_count); + bool deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description = ""); + + bool loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading = false); + + void loadLootContainer(const pugi::xml_node& node, LootBlock&); + bool loadLootItem(const pugi::xml_node& node, LootBlock&); + + std::map monsters; + std::unique_ptr scriptInterface; + + bool loaded = false; +}; + +#endif diff --git a/src/movement.cpp b/src/movement.cpp new file mode 100644 index 0000000..9c0848c --- /dev/null +++ b/src/movement.cpp @@ -0,0 +1,876 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "game.h" + +#include "pugicast.h" + +#include "movement.h" + +extern Game g_game; +extern Vocations g_vocations; + +MoveEvents::MoveEvents() : + scriptInterface("MoveEvents Interface") +{ + scriptInterface.initState(); +} + +MoveEvents::~MoveEvents() +{ + clear(); +} + +void MoveEvents::clearMap(MoveListMap& map) +{ + std::unordered_set set; + for (const auto& it : map) { + const MoveEventList& moveEventList = it.second; + for (const auto& i : moveEventList.moveEvent) { + for (MoveEvent* moveEvent : i) { + set.insert(moveEvent); + } + } + } + map.clear(); + + for (MoveEvent* moveEvent : set) { + delete moveEvent; + } +} + +void MoveEvents::clear() +{ + clearMap(itemIdMap); + clearMap(movementIdMap); + + for (const auto& it : positionMap) { + const MoveEventList& moveEventList = it.second; + for (const auto& i : moveEventList.moveEvent) { + for (MoveEvent* moveEvent : i) { + delete moveEvent; + } + } + } + positionMap.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& MoveEvents::getScriptInterface() +{ + return scriptInterface; +} + +std::string MoveEvents::getScriptBaseName() const +{ + return "movements"; +} + +Event* MoveEvents::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "movevent") != 0) { + return nullptr; + } + return new MoveEvent(&scriptInterface); +} + +bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) +{ + MoveEvent* moveEvent = static_cast(event); //event is guaranteed to be a MoveEvent + + const MoveEvent_t eventType = moveEvent->getEventType(); + if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { + pugi::xml_attribute tileItemAttribute = node.attribute("tileitem"); + if (tileItemAttribute && pugi::cast(tileItemAttribute.value()) == 1) { + switch (eventType) { + case MOVE_EVENT_ADD_ITEM: + moveEvent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + break; + case MOVE_EVENT_REMOVE_ITEM: + moveEvent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + break; + default: + break; + } + } + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("itemid"))) { + int32_t id = pugi::cast(attr.value()); + addEvent(moveEvent, id, itemIdMap); + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + } else if ((attr = node.attribute("fromid"))) { + uint32_t id = pugi::cast(attr.value()); + uint32_t endId = pugi::cast(node.attribute("toid").value()); + + addEvent(moveEvent, id, itemIdMap); + + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + + while (++id <= endId) { + addEvent(moveEvent, id, itemIdMap); + + ItemType& tit = Item::items.getItemType(id); + tit.wieldInfo = moveEvent->getWieldInfo(); + tit.minReqLevel = moveEvent->getReqLevel(); + tit.minReqMagicLevel = moveEvent->getReqMagLv(); + tit.vocationString = moveEvent->getVocationString(); + } + } else { + while (++id <= endId) { + addEvent(moveEvent, id, itemIdMap); + } + } + } else if ((attr = node.attribute("movementid"))) { + addEvent(moveEvent, pugi::cast(attr.value()), movementIdMap); + } else if ((attr = node.attribute("frommovementid"))) { + uint32_t id = pugi::cast(attr.value()); + uint32_t endId = pugi::cast(node.attribute("tomovementid").value()); + addEvent(moveEvent, id, movementIdMap); + while (++id <= endId) { + addEvent(moveEvent, id, movementIdMap); + } + } else if ((attr = node.attribute("pos"))) { + std::vector posList = vectorAtoi(explodeString(attr.as_string(), ";")); + if (posList.size() < 3) { + return false; + } + + Position pos(posList[0], posList[1], posList[2]); + addEvent(moveEvent, pos, positionMap); + } else { + return false; + } + return true; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map) +{ + auto it = map.find(id); + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[id] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + for (MoveEvent* existingMoveEvent : moveEventList) { + if (existingMoveEvent->getSlot() == moveEvent->getSlot()) { + std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << id << std::endl; + } + } + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) +{ + uint32_t slotp; + switch (slot) { + case CONST_SLOT_HEAD: slotp = SLOTP_HEAD; break; + case CONST_SLOT_NECKLACE: slotp = SLOTP_NECKLACE; break; + case CONST_SLOT_BACKPACK: slotp = SLOTP_BACKPACK; break; + case CONST_SLOT_ARMOR: slotp = SLOTP_ARMOR; break; + case CONST_SLOT_RIGHT: slotp = SLOTP_RIGHT; break; + case CONST_SLOT_LEFT: slotp = SLOTP_LEFT; break; + case CONST_SLOT_LEGS: slotp = SLOTP_LEGS; break; + case CONST_SLOT_FEET: slotp = SLOTP_FEET; break; + case CONST_SLOT_AMMO: slotp = SLOTP_AMMO; break; + case CONST_SLOT_RING: slotp = SLOTP_RING; break; + default: slotp = 0; break; + } + + auto it = itemIdMap.find(item->getID()); + if (it != itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + for (MoveEvent* moveEvent : moveEventList) { + if ((moveEvent->getSlot() & slotp) != 0) { + return moveEvent; + } + } + } + return nullptr; +} + +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType) +{ + MoveListMap::iterator it; + + if (item->hasAttribute(ITEM_ATTRIBUTE_MOVEMENTID)) { + it = movementIdMap.find(item->getMovementId()); + if (it != movementIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + } + + if (!item->hasCollisionEvent() && !item->hasSeparationEvent()) { + return nullptr; + } + + it = itemIdMap.find(item->getID()); + if (it != itemIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + + return nullptr; +} + +void MoveEvents::addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map) +{ + auto it = map.find(pos); + if (it == map.end()) { + MoveEventList moveEventList; + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); + map[pos] = moveEventList; + } else { + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + if (!moveEventList.empty()) { + std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl; + } + + moveEventList.push_back(moveEvent); + } +} + +MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType) +{ + auto it = positionMap.find(tile->getPosition()); + if (it != positionMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return *moveEventList.begin(); + } + } + return nullptr; +} + +uint32_t MoveEvents::onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType) +{ + const Position& pos = tile->getPosition(); + + uint32_t ret = 1; + + MoveEvent* moveEvent = getEvent(tile, eventType); + if (moveEvent) { + ret &= moveEvent->fireStepEvent(creature, nullptr, pos); + } + + for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) { + Thing* thing = tile->getThing(i); + if (!thing) { + continue; + } + + Item* tileItem = thing->getItem(); + if (!tileItem) { + continue; + } + + moveEvent = getEvent(tileItem, eventType); + if (moveEvent) { + ret &= moveEvent->fireStepEvent(creature, tileItem, pos); + } + } + return ret; +} + +uint32_t MoveEvents::onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_EQUIP, slot); + if (!moveEvent) { + return 1; + } + return moveEvent->fireEquip(player, item, slot, isCheck); +} + +uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot) +{ + MoveEvent* moveEvent = getEvent(item, MOVE_EVENT_DEEQUIP, slot); + if (!moveEvent) { + return 1; + } + return moveEvent->fireEquip(player, item, slot, true); +} + +uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) +{ + MoveEvent_t eventType1, eventType2; + if (isAdd) { + eventType1 = MOVE_EVENT_ADD_ITEM; + eventType2 = MOVE_EVENT_ADD_ITEM_ITEMTILE; + } else { + eventType1 = MOVE_EVENT_REMOVE_ITEM; + eventType2 = MOVE_EVENT_REMOVE_ITEM_ITEMTILE; + } + + uint32_t ret = 1; + MoveEvent* moveEvent = getEvent(tile, eventType1); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition()); + } + + moveEvent = getEvent(item, eventType1); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, nullptr, tile->getPosition()); + } + + for (size_t i = tile->getFirstIndex(), j = tile->getLastIndex(); i < j; ++i) { + Thing* thing = tile->getThing(i); + if (!thing) { + continue; + } + + Item* tileItem = thing->getItem(); + if (!tileItem || tileItem == item) { + continue; + } + + moveEvent = getEvent(tileItem, eventType2); + if (moveEvent) { + ret &= moveEvent->fireAddRemItem(item, tileItem, tile->getPosition()); + } + } + return ret; +} + +MoveEvent::MoveEvent(LuaScriptInterface* interface) : Event(interface) {} + +MoveEvent::MoveEvent(const MoveEvent* copy) : + Event(copy), + eventType(copy->eventType), + stepFunction(copy->stepFunction), + moveFunction(copy->moveFunction), + equipFunction(copy->equipFunction), + slot(copy->slot), + reqLevel(copy->reqLevel), + reqMagLevel(copy->reqMagLevel), + premium(copy->premium), + vocationString(copy->vocationString), + wieldInfo(copy->wieldInfo), + vocEquipMap(copy->vocEquipMap) {} + +std::string MoveEvent::getScriptEventName() const +{ + switch (eventType) { + case MOVE_EVENT_STEP_IN: return "onStepIn"; + case MOVE_EVENT_STEP_OUT: return "onStepOut"; + case MOVE_EVENT_EQUIP: return "onEquip"; + case MOVE_EVENT_DEEQUIP: return "onDeEquip"; + case MOVE_EVENT_ADD_ITEM: return "onAddItem"; + case MOVE_EVENT_REMOVE_ITEM: return "onRemoveItem"; + default: + std::cout << "[Error - MoveEvent::getScriptEventName] Invalid event type" << std::endl; + return std::string(); + } +} + +bool MoveEvent::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute eventAttr = node.attribute("event"); + if (!eventAttr) { + std::cout << "[Error - MoveEvent::configureMoveEvent] Missing event" << std::endl; + return false; + } + + std::string tmpStr = asLowerCaseString(eventAttr.as_string()); + if (tmpStr == "stepin") { + eventType = MOVE_EVENT_STEP_IN; + } else if (tmpStr == "stepout") { + eventType = MOVE_EVENT_STEP_OUT; + } else if (tmpStr == "equip") { + eventType = MOVE_EVENT_EQUIP; + } else if (tmpStr == "deequip") { + eventType = MOVE_EVENT_DEEQUIP; + } else if (tmpStr == "additem") { + eventType = MOVE_EVENT_ADD_ITEM; + } else if (tmpStr == "removeitem") { + eventType = MOVE_EVENT_REMOVE_ITEM; + } else { + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << eventAttr.as_string() << std::endl; + return false; + } + + if (eventType == MOVE_EVENT_EQUIP || eventType == MOVE_EVENT_DEEQUIP) { + pugi::xml_attribute slotAttribute = node.attribute("slot"); + if (slotAttribute) { + tmpStr = asLowerCaseString(slotAttribute.as_string()); + if (tmpStr == "head") { + slot = SLOTP_HEAD; + } else if (tmpStr == "necklace") { + slot = SLOTP_NECKLACE; + } else if (tmpStr == "backpack") { + slot = SLOTP_BACKPACK; + } else if (tmpStr == "armor") { + slot = SLOTP_ARMOR; + } else if (tmpStr == "right-hand") { + slot = SLOTP_RIGHT; + } else if (tmpStr == "left-hand") { + slot = SLOTP_LEFT; + } else if (tmpStr == "hand" || tmpStr == "shield") { + slot = SLOTP_RIGHT | SLOTP_LEFT; + } else if (tmpStr == "legs") { + slot = SLOTP_LEGS; + } else if (tmpStr == "feet") { + slot = SLOTP_FEET; + } else if (tmpStr == "ring") { + slot = SLOTP_RING; + } else if (tmpStr == "ammo") { + slot = SLOTP_AMMO; + } else { + std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << slotAttribute.as_string() << std::endl; + } + } + + wieldInfo = 0; + + pugi::xml_attribute levelAttribute = node.attribute("level"); + if (levelAttribute) { + reqLevel = pugi::cast(levelAttribute.value()); + if (reqLevel > 0) { + wieldInfo |= WIELDINFO_LEVEL; + } + } + + pugi::xml_attribute magLevelAttribute = node.attribute("maglevel"); + if (magLevelAttribute) { + reqMagLevel = pugi::cast(magLevelAttribute.value()); + if (reqMagLevel > 0) { + wieldInfo |= WIELDINFO_MAGLV; + } + } + + pugi::xml_attribute premiumAttribute = node.attribute("premium"); + if (premiumAttribute) { + premium = premiumAttribute.as_bool(); + if (premium) { + wieldInfo |= WIELDINFO_PREMIUM; + } + } + + //Gather vocation information + std::list vocStringList; + for (auto vocationNode : node.children()) { + pugi::xml_attribute vocationNameAttribute = vocationNode.attribute("name"); + if (!vocationNameAttribute) { + continue; + } + + int32_t vocationId = g_vocations.getVocationId(vocationNameAttribute.as_string()); + if (vocationId != -1) { + vocEquipMap[vocationId] = true; + if (vocationNode.attribute("showInDescription").as_bool(true)) { + vocStringList.push_back(asLowerCaseString(vocationNameAttribute.as_string())); + } + } + } + + if (!vocEquipMap.empty()) { + wieldInfo |= WIELDINFO_VOCREQ; + } + + for (const std::string& str : vocStringList) { + if (!vocationString.empty()) { + if (str != vocStringList.back()) { + vocationString.push_back(','); + vocationString.push_back(' '); + } else { + vocationString += " and "; + } + } + + vocationString += str; + vocationString.push_back('s'); + } + } + return true; +} + +bool MoveEvent::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "onstepinfield") == 0) { + stepFunction = StepInField; + } else if (strcasecmp(functionName, "onstepoutfield") == 0) { + stepFunction = StepOutField; + } else if (strcasecmp(functionName, "onaddfield") == 0) { + moveFunction = AddItemField; + } else if (strcasecmp(functionName, "onremovefield") == 0) { + moveFunction = RemoveItemField; + } else if (strcasecmp(functionName, "onequipitem") == 0) { + equipFunction = EquipItem; + } else if (strcasecmp(functionName, "ondeequipitem") == 0) { + equipFunction = DeEquipItem; + } else { + std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +MoveEvent_t MoveEvent::getEventType() const +{ + return eventType; +} + +void MoveEvent::setEventType(MoveEvent_t type) +{ + eventType = type; +} + +uint32_t MoveEvent::StepInField(Creature* creature, Item* item, const Position&) +{ + MagicField* field = item->getMagicField(); + if (field) { + field->onStepInField(creature); + return 1; + } + + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::StepOutField(Creature*, Item*, const Position&) +{ + return 1; +} + +uint32_t MoveEvent::AddItemField(Item* item, Item*, const Position&) +{ + if (MagicField* field = item->getMagicField()) { + Tile* tile = item->getTile(); + if (CreatureVector* creatures = tile->getCreatures()) { + for (Creature* creature : *creatures) { + field->onStepInField(creature); + } + } + return 1; + } + return LUA_ERROR_ITEM_NOT_FOUND; +} + +uint32_t MoveEvent::RemoveItemField(Item*, Item*, const Position&) +{ + return 1; +} + +uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool isCheck) +{ + if (player->isItemAbilityEnabled(slot)) { + return 1; + } + + if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck) && moveEvent->getWieldInfo() != 0) { + if (player->getLevel() < moveEvent->getReqLevel() || player->getMagicLevel() < moveEvent->getReqMagLv()) { + return 0; + } + + if (moveEvent->isPremium() && !player->isPremium()) { + return 0; + } + + const VocEquipMap& vocEquipMap = moveEvent->getVocEquipMap(); + if (!vocEquipMap.empty() && vocEquipMap.find(player->getVocationId()) == vocEquipMap.end()) { + return 0; + } + } + + if (isCheck) { + return 1; + } + + const ItemType& it = Item::items[item->getID()]; + if (it.transformEquipTo != 0) { + Item* newItem = g_game.transformItem(item, it.transformEquipTo); + g_game.startDecay(newItem); + } else { + player->setItemAbility(slot, true); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_INVISIBLE, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->manaShield) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_MANASHIELD, -1, 0); + player->addCondition(condition); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->addConditionSuppressions(it.abilities->conditionSuppressions); + player->sendIcons(); + } + + if (it.abilities->regeneration) { + Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); + + if (it.abilities->healthGain != 0) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain); + } + + if (it.abilities->healthTicks != 0) { + condition->setParam(CONDITION_PARAM_HEALTHTICKS, it.abilities->healthTicks); + } + + if (it.abilities->manaGain != 0) { + condition->setParam(CONDITION_PARAM_MANAGAIN, it.abilities->manaGain); + } + + if (it.abilities->manaTicks != 0) { + condition->setParam(CONDITION_PARAM_MANATICKS, it.abilities->manaTicks); + } + + player->addCondition(condition); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i]) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t slot, bool) +{ + if (!player->isItemAbilityEnabled(slot)) { + return 1; + } + + player->setItemAbility(slot, false); + + const ItemType& it = Item::items[item->getID()]; + if (it.transformDeEquipTo != 0) { + g_game.transformItem(item, it.transformDeEquipTo); + g_game.startDecay(item); + } + + if (!it.abilities) { + return 1; + } + + if (it.abilities->invisible) { + player->removeCondition(CONDITION_INVISIBLE, static_cast(slot)); + } + + if (it.abilities->manaShield) { + player->removeCondition(CONDITION_MANASHIELD, static_cast(slot)); + } + + if (it.abilities->speed != 0) { + g_game.changeSpeed(player, -it.abilities->speed); + } + + if (it.abilities->conditionSuppressions != 0) { + player->removeConditionSuppressions(it.abilities->conditionSuppressions); + player->sendIcons(); + } + + if (it.abilities->regeneration) { + player->removeCondition(CONDITION_REGENERATION, static_cast(slot)); + } + + //skill modifiers + bool needUpdateSkills = false; + + for (int32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (it.abilities->skills[i] != 0) { + needUpdateSkills = true; + player->setVarSkill(static_cast(i), -it.abilities->skills[i]); + } + } + + if (needUpdateSkills) { + player->sendSkills(); + } + + //stat modifiers + bool needUpdateStats = false; + + for (int32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (it.abilities->stats[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), -it.abilities->stats[s]); + } + + if (it.abilities->statsPercent[s]) { + needUpdateStats = true; + player->setVarStats(static_cast(s), -static_cast(player->getDefaultStats(static_cast(s)) * ((it.abilities->statsPercent[s] - 100) / 100.f))); + } + } + + if (needUpdateStats) { + player->sendStats(); + } + + return 1; +} + +uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) +{ + if (scripted) { + return executeStep(creature, item, pos); + } else { + return stepFunction(creature, item, pos); + } +} + +bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) +{ + //onStepIn(creature, item, pos, fromPosition) + //onStepOut(creature, item, pos, fromPosition) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeStep] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushPosition(L, pos); + LuaScriptInterface::pushPosition(L, creature->getLastPosition()); + + return scriptInterface->callFunction(4); +} + +uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean) +{ + if (scripted) { + return executeEquip(player, item, slot); + } else { + return equipFunction(this, player, item, slot, boolean); + } +} + +bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) +{ + //onEquip(player, item, slot) + //onDeEquip(player, item, slot) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + LuaScriptInterface::pushThing(L, item); + lua_pushnumber(L, slot); + + return scriptInterface->callFunction(3); +} + +uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + if (scripted) { + return executeAddRemItem(item, tileItem, pos); + } else { + return moveFunction(item, tileItem, pos); + } +} + +bool MoveEvent::executeAddRemItem(Item* item, Item* tileItem, const Position& pos) +{ + //onaddItem(moveitem, tileitem, pos) + //onRemoveItem(moveitem, tileitem, pos) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - MoveEvent::executeAddRemItem] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushThing(L, tileItem); + LuaScriptInterface::pushPosition(L, pos); + + return scriptInterface->callFunction(3); +} diff --git a/src/movement.h b/src/movement.h new file mode 100644 index 0000000..cb24693 --- /dev/null +++ b/src/movement.h @@ -0,0 +1,168 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 +#define FS_MOVEMENT_H_5E0D2626D4634ACA83AC6509518E5F49 + +#include "baseevents.h" +#include "item.h" +#include "luascript.h" + +enum MoveEvent_t { + MOVE_EVENT_STEP_IN, + MOVE_EVENT_STEP_OUT, + MOVE_EVENT_EQUIP, + MOVE_EVENT_DEEQUIP, + MOVE_EVENT_ADD_ITEM, + MOVE_EVENT_REMOVE_ITEM, + MOVE_EVENT_ADD_ITEM_ITEMTILE, + MOVE_EVENT_REMOVE_ITEM_ITEMTILE, + + MOVE_EVENT_LAST, + MOVE_EVENT_NONE +}; + +class MoveEvent; + +struct MoveEventList { + std::list moveEvent[MOVE_EVENT_LAST]; +}; + +typedef std::map VocEquipMap; + +class MoveEvents final : public BaseEvents +{ + public: + MoveEvents(); + ~MoveEvents(); + + // non-copyable + MoveEvents(const MoveEvents&) = delete; + MoveEvents& operator=(const MoveEvents&) = delete; + + uint32_t onCreatureMove(Creature* creature, const Tile* tile, MoveEvent_t eventType); + uint32_t onPlayerEquip(Player* player, Item* item, slots_t slot, bool isCheck); + uint32_t onPlayerDeEquip(Player* player, Item* item, slots_t slot); + uint32_t onItemMove(Item* item, Tile* tile, bool isAdd); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType); + + protected: + typedef std::map MoveListMap; + void clearMap(MoveListMap& map); + + typedef std::map MovePosListMap; + void clear() final; + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map); + + void addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map); + MoveEvent* getEvent(const Tile* tile, MoveEvent_t eventType); + + MoveEvent* getEvent(Item* item, MoveEvent_t eventType, slots_t slot); + + + MoveListMap movementIdMap; + MoveListMap itemIdMap; + MovePosListMap positionMap; + + LuaScriptInterface scriptInterface; +}; + +typedef uint32_t (StepFunction)(Creature* creature, Item* item, const Position& pos); +typedef uint32_t (MoveFunction)(Item* item, Item* tileItem, const Position& pos); +typedef uint32_t (EquipFunction)(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); + +class MoveEvent final : public Event +{ + public: + explicit MoveEvent(LuaScriptInterface* interface); + explicit MoveEvent(const MoveEvent* copy); + + MoveEvent_t getEventType() const; + void setEventType(MoveEvent_t type); + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + uint32_t fireStepEvent(Creature* creature, Item* item, const Position& pos); + uint32_t fireAddRemItem(Item* item, Item* tileItem, const Position& pos); + uint32_t fireEquip(Player* player, Item* item, slots_t slot, bool boolean); + + uint32_t getSlot() const { + return slot; + } + + //scripting + bool executeStep(Creature* creature, Item* item, const Position& pos); + bool executeEquip(Player* player, Item* item, slots_t slot); + bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); + // + + //onEquip information + uint32_t getReqLevel() const { + return reqLevel; + } + uint32_t getReqMagLv() const { + return reqMagLevel; + } + bool isPremium() const { + return premium; + } + const std::string& getVocationString() const { + return vocationString; + } + uint32_t getWieldInfo() const { + return wieldInfo; + } + const VocEquipMap& getVocEquipMap() const { + return vocEquipMap; + } + + protected: + std::string getScriptEventName() const final; + + static StepFunction StepInField; + static StepFunction StepOutField; + + static MoveFunction AddItemField; + static MoveFunction RemoveItemField; + static EquipFunction EquipItem; + static EquipFunction DeEquipItem; + + MoveEvent_t eventType = MOVE_EVENT_NONE; + StepFunction* stepFunction = nullptr; + MoveFunction* moveFunction = nullptr; + EquipFunction* equipFunction = nullptr; + uint32_t slot = SLOTP_WHEREEVER; + + //onEquip information + uint32_t reqLevel = 0; + uint32_t reqMagLevel = 0; + bool premium = false; + std::string vocationString; + uint32_t wieldInfo = 0; + VocEquipMap vocEquipMap; +}; + +#endif diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp new file mode 100644 index 0000000..12c0fdb --- /dev/null +++ b/src/networkmessage.cpp @@ -0,0 +1,135 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "networkmessage.h" + +#include "container.h" +#include "creature.h" + +std::string NetworkMessage::getString(uint16_t stringLen/* = 0*/) +{ + if (stringLen == 0) { + stringLen = get(); + } + + if (!canRead(stringLen)) { + return std::string(); + } + + char* v = reinterpret_cast(buffer) + info.position; //does not break strict aliasing + info.position += stringLen; + return std::string(v, stringLen); +} + +Position NetworkMessage::getPosition() +{ + Position pos; + pos.x = get(); + pos.y = get(); + pos.z = getByte(); + return pos; +} + +void NetworkMessage::addString(const std::string& value) +{ + size_t stringLen = value.length(); + if (!canAdd(stringLen + 2) || stringLen > 8192) { + return; + } + + add(stringLen); + memcpy(buffer + info.position, value.c_str(), stringLen); + info.position += stringLen; + info.length += stringLen; +} + +void NetworkMessage::addDouble(double value, uint8_t precision/* = 2*/) +{ + addByte(precision); + add((value * std::pow(static_cast(10), precision)) + std::numeric_limits::max()); +} + +void NetworkMessage::addBytes(const char* bytes, size_t size) +{ + if (!canAdd(size) || size > 8192) { + return; + } + + memcpy(buffer + info.position, bytes, size); + info.position += size; + info.length += size; +} + +void NetworkMessage::addPaddingBytes(size_t n) +{ + if (!canAdd(n)) { + return; + } + + memset(buffer + info.position, 0x33, n); + info.length += n; +} + +void NetworkMessage::addPosition(const Position& pos) +{ + add(pos.x); + add(pos.y); + addByte(pos.z); +} + +void NetworkMessage::addItem(uint16_t id, uint8_t count) +{ + const ItemType& it = Item::items[id]; + + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } + + if (it.stackable) { + addByte(count); + } else if (it.isSplash() || it.isFluidContainer()) { + addByte(getLiquidColor(count)); + } +} + +void NetworkMessage::addItem(const Item* item) +{ + const ItemType& it = Item::items[item->getID()]; + + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } + + if (it.stackable) { + addByte(std::min(0xFF, item->getItemCount())); + } else if (it.isSplash() || it.isFluidContainer()) { + addByte(getLiquidColor(item->getFluidType())); + } +} + +void NetworkMessage::addItemId(uint16_t itemId) +{ + add(itemId); +} diff --git a/src/networkmessage.h b/src/networkmessage.h new file mode 100644 index 0000000..e64fa30 --- /dev/null +++ b/src/networkmessage.h @@ -0,0 +1,173 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 +#define FS_NETWORKMESSAGE_H_B853CFED58D1413A87ACED07B2926E03 + +#include "const.h" + +class Item; +class Creature; +class Player; +struct Position; +class RSA; + +class NetworkMessage +{ + public: + typedef uint16_t MsgSize_t; + // Headers: + // 2 bytes for unencrypted message size + // 2 bytes for encrypted message size + static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 4; + enum { HEADER_LENGTH = 2 }; + enum { XTEA_MULTIPLE = 8 }; + enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - XTEA_MULTIPLE }; + enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 }; + + NetworkMessage() = default; + + void reset() { + info = {}; + } + + // simply read functions for incoming message + uint8_t getByte() { + if (!canRead(1)) { + return 0; + } + + return buffer[info.position++]; + } + + uint8_t getPreviousByte() { + return buffer[--info.position]; + } + + template + T get() { + if (!canRead(sizeof(T))) { + return 0; + } + + T v; + memcpy(&v, buffer + info.position, sizeof(T)); + info.position += sizeof(T); + return v; + } + + std::string getString(uint16_t stringLen = 0); + Position getPosition(); + + // skips count unknown/unused bytes in an incoming message + void skipBytes(int16_t count) { + info.position += count; + } + + // simply write functions for outgoing message + void addByte(uint8_t value) { + if (!canAdd(1)) { + return; + } + + buffer[info.position++] = value; + info.length++; + } + + template + void add(T value) { + if (!canAdd(sizeof(T))) { + return; + } + + memcpy(buffer + info.position, &value, sizeof(T)); + info.position += sizeof(T); + info.length += sizeof(T); + } + + void addBytes(const char* bytes, size_t size); + void addPaddingBytes(size_t n); + + void addString(const std::string& value); + + void addDouble(double value, uint8_t precision = 2); + + // write functions for complex types + void addPosition(const Position& pos); + void addItem(uint16_t id, uint8_t count); + void addItem(const Item* item); + void addItemId(uint16_t itemId); + + MsgSize_t getLength() const { + return info.length; + } + + void setLength(MsgSize_t newLength) { + info.length = newLength; + } + + MsgSize_t getBufferPosition() const { + return info.position; + } + + uint16_t getLengthHeader() const { + return static_cast(buffer[0] | buffer[1] << 8); + } + + bool isOverrun() const { + return info.overrun; + } + + uint8_t* getBuffer() { + return buffer; + } + + const uint8_t* getBuffer() const { + return buffer; + } + + uint8_t* getBodyBuffer() { + info.position = 2; + return buffer + HEADER_LENGTH; + } + + protected: + inline bool canAdd(size_t size) const { + return (size + info.position) < MAX_BODY_LENGTH; + } + + inline bool canRead(int32_t size) { + if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { + info.overrun = true; + return false; + } + return true; + } + + struct NetworkMessageInfo { + MsgSize_t length = 0; + MsgSize_t position = INITIAL_BUFFER_POSITION; + bool overrun = false; + }; + + NetworkMessageInfo info; + uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; +}; + +#endif // #ifndef __NETWORK_MESSAGE_H__ diff --git a/src/npc.cpp b/src/npc.cpp new file mode 100644 index 0000000..cb2cf7b --- /dev/null +++ b/src/npc.cpp @@ -0,0 +1,482 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "npc.h" +#include "game.h" +#include "tools.h" +#include "position.h" +#include "player.h" +#include "spawn.h" +#include "script.h" +#include "behaviourdatabase.h" + +extern Game g_game; + +uint32_t Npc::npcAutoID = 0x80000000; + +void Npcs::loadNpcs() +{ + std::cout << ">> Loading npcs..." << std::endl; + + std::vector files; + getFilesInDirectory("data/npc/", ".npc", files); + for (auto file : files) + { + std::string npcName = file.filename().string(); + int32_t end = npcName.find_first_of('/'); + npcName = npcName.substr(end + 1, npcName.length() - end); + end = npcName.find_first_of('.'); + npcName = npcName.substr(0, end); + + Npc* npc = Npc::createNpc(npcName); + if (!npc) { + return; + } + + g_game.placeCreature(npc, npc->getMasterPos(), false, true); + } +} + +void Npcs::reload() +{ + const std::map& npcs = g_game.getNpcs(); + + for (const auto& it : npcs) { + it.second->reload(); + } +} + +Npc* Npc::createNpc(const std::string& name) +{ + std::unique_ptr npc(new Npc(name)); + npc->filename = "data/npc/" + name + ".npc"; + if (!npc->load()) { + return nullptr; + } + + return npc.release(); +} + +Npc::Npc(const std::string& name) : + Creature(), + filename("data/npc/" + name + ".npc"), + masterRadius(0), + staticMovementTime(0), + loaded(false), + behaviourDatabase(nullptr) +{ + baseSpeed = 5; + reset(); +} + +Npc::~Npc() +{ + reset(); +} + +void Npc::addList() +{ + g_game.addNpc(this); +} + +void Npc::removeList() +{ + g_game.removeNpc(this); +} + +bool Npc::load() +{ + if (loaded) { + return true; + } + + reset(); + + ScriptReader script; + if (!script.open(filename)) { + return false; + } + + while (true) { + script.nextToken(); + + if (script.Token == ENDOFFILE) { + break; + } + + if (script.Token != IDENTIFIER) { + script.error("identifier expected"); + return false; + } + + std::string ident = script.getIdentifier(); + script.readSymbol('='); + + if (ident == "name") { + name = script.readString(); + } else if (ident == "outfit") { + script.readSymbol('('); + uint8_t* c; + currentOutfit.lookType = script.readNumber(); + script.readSymbol(','); + if (currentOutfit.lookType > 0) { + c = script.readBytesequence(); + currentOutfit.lookHead = c[0]; + currentOutfit.lookBody = c[1]; + currentOutfit.lookLegs = c[2]; + currentOutfit.lookFeet = c[3]; + } else { + currentOutfit.lookTypeEx = script.readNumber(); + } + script.readSymbol(')'); + } else if (ident == "home") { + script.readCoordinate(masterPos.x, masterPos.y, masterPos.z); + } else if (ident == "radius") { + masterRadius = script.readNumber(); + } else if (ident == "behaviour") { + if (behaviourDatabase) { + script.error("behaviour database already defined"); + return false; + } + + behaviourDatabase = new BehaviourDatabase(this); + if (!behaviourDatabase->loadDatabase(script)) { + return false; + } + } + } + + script.close(); + return true; +} + +void Npc::reset() +{ + loaded = false; + focusCreature = 0; + conversationEndTime = 0; + + if (behaviourDatabase) { + delete behaviourDatabase; + behaviourDatabase = nullptr; + } +} + +void Npc::reload() +{ + loaded = false; + + reset(); + load(); + + if (baseSpeed > 0) { + addEventWalk(); + } +} + +bool Npc::canSee(const Position& pos) const +{ + if (pos.z != getPosition().z) { + return false; + } + return Creature::canSee(getPosition(), pos, 3, 3); +} + +std::string Npc::getDescription(int32_t) const +{ + std::string descr; + descr.reserve(name.length() + 1); + descr.assign(name); + descr.push_back('.'); + return descr; +} + +void Npc::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (creature == this) { + if (baseSpeed > 0) { + addEventWalk(); + } + } else if (Player* player = creature->getPlayer()) { + spectators.insert(player); + updateIdleStatus(); + } +} + +void Npc::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (!behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player) { + if (player->getID() == focusCreature) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + + spectators.erase(player); + updateIdleStatus(); + } +} + +void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (!behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player && player->getID() == focusCreature) { + if (!Position::areInRange<3, 3, 0>(creature->getPosition(), getPosition())) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + } + + if (creature != this) { + if (player) { + if (player->canSee(position)) { + spectators.insert(player); + } else { + spectators.erase(player); + } + + updateIdleStatus(); + } + } +} + +void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) +{ + if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player) { + if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) { + return; + } + + lastTalkCreature = creature->getID(); + + if (focusCreature == 0) { + behaviourDatabase->react(SITUATION_ADDRESS, player, text); + } else if (focusCreature != player->getID()) { + behaviourDatabase->react(SITUATION_BUSY, player, text); + } else if (focusCreature == player->getID()) { + behaviourDatabase->react(SITUATION_NONE, player, text); + } + } +} + +void Npc::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) { + addEventWalk(); + } + + if (!behaviourDatabase) { + return; + } + + if (focusCreature) { + Player* player = g_game.getPlayerByID(focusCreature); + if (player) { + turnToCreature(player); + + if (conversationEndTime != 0 && OTSYS_TIME() > conversationEndTime) { + if (player) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); + } + } + } + } +} + +void Npc::doSay(const std::string& text) +{ + if (lastTalkCreature == focusCreature) { + conversationEndTime = OTSYS_TIME() + 60000; + } + + g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false); +} + +bool Npc::getNextStep(Direction& dir, uint32_t& flags) +{ + if (Creature::getNextStep(dir, flags)) { + return true; + } + + if (baseSpeed <= 0) { + return false; + } + + if (focusCreature != 0) { + return false; + } + + if (OTSYS_TIME() < staticMovementTime) { + return false; + } + + if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) { + return false; + } + + return getRandomStep(dir); +} + +void Npc::setIdle(bool idle) +{ + if (isRemoved() || getHealth() <= 0) { + return; + } + + isIdle = idle; + + if (isIdle) { + onIdleStatus(); + } +} + +void Npc::updateIdleStatus() +{ + bool status = spectators.empty(); + if (status != isIdle) { + setIdle(status); + } +} + +bool Npc::canWalkTo(const Position& fromPos, Direction dir) const +{ + if (masterRadius == 0) { + return false; + } + + Position toPos = getNextPosition(dir, fromPos); + if (!Spawns::isInZone(masterPos, masterRadius, toPos)) { + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile || tile->queryAdd(0, *this, 1, 0) != RETURNVALUE_NOERROR) { + return false; + } + + if (tile->hasFlag(TILESTATE_BLOCKPATH)) { + return false; + } + + if (tile->hasHeight(1)) { + return false; + } + + return true; +} + +bool Npc::getRandomStep(Direction& dir) const +{ + std::vector dirList; + const Position& creaturePos = getPosition(); + + if (canWalkTo(creaturePos, DIRECTION_NORTH)) { + dirList.push_back(DIRECTION_NORTH); + } + + if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { + dirList.push_back(DIRECTION_SOUTH); + } + + if (canWalkTo(creaturePos, DIRECTION_EAST)) { + dirList.push_back(DIRECTION_EAST); + } + + if (canWalkTo(creaturePos, DIRECTION_WEST)) { + dirList.push_back(DIRECTION_WEST); + } + + if (dirList.empty()) { + return false; + } + + dir = dirList[uniform_random(0, dirList.size() - 1)]; + return true; +} + +void Npc::doMoveTo(const Position& target) +{ + std::forward_list listDir; + if (getPathTo(target, listDir, 1, 1, true, true)) { + startAutoWalk(listDir); + } +} + +void Npc::turnToCreature(Creature* creature) +{ + const Position& creaturePos = creature->getPosition(); + const Position& myPos = getPosition(); + const auto dx = Position::getOffsetX(myPos, creaturePos); + const auto dy = Position::getOffsetY(myPos, creaturePos); + + float tan; + if (dx != 0) { + tan = static_cast(dy) / dx; + } else { + tan = 10; + } + + Direction dir; + if (std::abs(tan) < 1) { + if (dx > 0) { + dir = DIRECTION_WEST; + } else { + dir = DIRECTION_EAST; + } + } else { + if (dy > 0) { + dir = DIRECTION_NORTH; + } else { + dir = DIRECTION_SOUTH; + } + } + g_game.internalCreatureTurn(this, dir); +} + +void Npc::setCreatureFocus(Creature* creature) +{ + if (creature) { + focusCreature = creature->getID(); + turnToCreature(creature); + } else { + focusCreature = 0; + } +} \ No newline at end of file diff --git a/src/npc.h b/src/npc.h new file mode 100644 index 0000000..737e143 --- /dev/null +++ b/src/npc.h @@ -0,0 +1,158 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_NPC_H_B090D0CB549D4435AFA03647195D156F +#define FS_NPC_H_B090D0CB549D4435AFA03647195D156F + +#include "creature.h" + +#include + +class Npc; +class Player; +class BehaviourDatabase; + +class Npcs +{ + public: + static void loadNpcs(); + static void reload(); +}; + +class Npc final : public Creature +{ + public: + ~Npc(); + + // non-copyable + Npc(const Npc&) = delete; + Npc& operator=(const Npc&) = delete; + + Npc* getNpc() final { + return this; + } + const Npc* getNpc() const final { + return this; + } + + bool isPushable() const final { + return baseSpeed > 0; + } + + void setID() final { + if (id == 0) { + id = npcAutoID++; + } + } + + void removeList() final; + void addList() final; + + static Npc* createNpc(const std::string& name); + + bool canSee(const Position& pos) const final; + + bool load(); + void reload(); + + const std::string& getName() const final { + return name; + } + const std::string& getNameDescription() const final { + return name; + } + + void doSay(const std::string& text); + + void doMoveTo(const Position& pos); + + int32_t getMasterRadius() const { + return masterRadius; + } + const Position& getMasterPos() const { + return masterPos; + } + void setMasterPos(Position pos, int32_t radius = 1) { + masterPos = pos; + if (masterRadius == 0) { + masterRadius = radius; + } + } + + void turnToCreature(Creature* creature); + void setCreatureFocus(Creature* creature); + + static uint32_t npcAutoID; + + protected: + explicit Npc(const std::string& name); + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) final; + + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) final; + void onThink(uint32_t interval) final; + std::string getDescription(int32_t lookDistance) const final; + + bool isImmune(CombatType_t) const final { + return true; + } + bool isImmune(ConditionType_t) const final { + return true; + } + bool isAttackable() const final { + return false; + } + bool getNextStep(Direction& dir, uint32_t& flags) final; + + void setIdle(bool idle); + void updateIdleStatus(); + + bool canWalkTo(const Position& fromPos, Direction dir) const; + bool getRandomStep(Direction& dir) const; + + void reset(); + + std::set spectators; + + std::string name; + std::string filename; + + Position masterPos; + + uint32_t lastTalkCreature; + uint32_t focusCreature; + uint32_t masterRadius; + + int64_t conversationStartTime; + int64_t conversationEndTime; + int64_t staticMovementTime; + + bool loaded; + bool isIdle; + + BehaviourDatabase* behaviourDatabase; + + friend class Npcs; + friend class BehaviourDatabase; +}; + +#endif diff --git a/src/otpch.cpp b/src/otpch.cpp new file mode 100644 index 0000000..323a370 --- /dev/null +++ b/src/otpch.cpp @@ -0,0 +1,20 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" \ No newline at end of file diff --git a/src/otpch.h b/src/otpch.h new file mode 100644 index 0000000..e1de8c9 --- /dev/null +++ b/src/otpch.h @@ -0,0 +1,44 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define FS_OTPCH_H_F00C737DA6CA4C8D90F57430C614367F + +// Definitions should be global. +#include "definitions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include diff --git a/src/otserv.cpp b/src/otserv.cpp new file mode 100644 index 0000000..bf202d6 --- /dev/null +++ b/src/otserv.cpp @@ -0,0 +1,281 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "server.h" + +#include "game.h" + +#ifndef _WIN32 +#include // for sigemptyset() +#endif + +#include "configmanager.h" +#include "scriptmanager.h" +#include "rsa.h" +#include "protocollogin.h" +#include "protocolstatus.h" +#include "databasemanager.h" +#include "scheduler.h" +#include "databasetasks.h" + +DatabaseTasks g_databaseTasks; +Dispatcher g_dispatcher; +Scheduler g_scheduler; + +Game g_game; +ConfigManager g_config; +Monsters g_monsters; +Vocations g_vocations; +RSA g_RSA; + +std::mutex g_loaderLock; +std::condition_variable g_loaderSignal; +std::unique_lock g_loaderUniqueLock(g_loaderLock); + +void startupErrorMessage(const std::string& errorStr) +{ + std::cout << "> ERROR: " << errorStr << std::endl; + g_loaderSignal.notify_all(); +} + +void mainLoader(int argc, char* argv[], ServiceManager* servicer); + +void badAllocationHandler() +{ + // Use functions that only use stack allocation + puts("Allocation failed, server out of memory.\nDecrease the size of your map or compile in 64 bits mode.\n"); + getchar(); + exit(-1); +} + +int main(int argc, char* argv[]) +{ + // Setup bad allocation handler + std::set_new_handler(badAllocationHandler); + +#ifndef _WIN32 + // ignore sigpipe... + struct sigaction sigh; + sigh.sa_handler = SIG_IGN; + sigh.sa_flags = 0; + sigemptyset(&sigh.sa_mask); + sigaction(SIGPIPE, &sigh, nullptr); +#endif + + ServiceManager serviceManager; + + g_dispatcher.start(); + g_scheduler.start(); + + g_dispatcher.addTask(createTask(std::bind(mainLoader, argc, argv, &serviceManager))); + + g_loaderSignal.wait(g_loaderUniqueLock); + + if (serviceManager.is_running()) { + std::cout << ">> " << g_config.getString(ConfigManager::SERVER_NAME) << " Server Online!" << std::endl << std::endl; +#ifdef _WIN32 + SetConsoleCtrlHandler([](DWORD) -> BOOL { + g_dispatcher.addTask(createTask([]() { + g_dispatcher.addTask(createTask( + std::bind(&Game::shutdown, &g_game) + )); + g_scheduler.stop(); + g_databaseTasks.stop(); + g_dispatcher.stop(); + })); + ExitThread(0); + }, 1); +#endif + serviceManager.run(); + } else { + std::cout << ">> No services running. The server is NOT online." << std::endl; + g_scheduler.shutdown(); + g_databaseTasks.shutdown(); + g_dispatcher.shutdown(); + } + + g_scheduler.join(); + g_databaseTasks.join(); + g_dispatcher.join(); + return 0; +} + +void mainLoader(int, char*[], ServiceManager* services) +{ + //dispatcher thread + g_game.setGameState(GAME_STATE_STARTUP); + + srand(static_cast(OTSYS_TIME())); +#ifdef _WIN32 + SetConsoleTitle(STATUS_SERVER_NAME); +#endif + std::cout << STATUS_SERVER_NAME << " - Version " << STATUS_SERVER_VERSION << std::endl; + std::cout << "Compiled with " << BOOST_COMPILER << std::endl; + std::cout << "Compiled on " << __DATE__ << ' ' << __TIME__ << " for platform "; + +#if defined(__amd64__) || defined(_M_X64) + std::cout << "x64" << std::endl; +#elif defined(__i386__) || defined(_M_IX86) || defined(_X86_) + std::cout << "x86" << std::endl; +#elif defined(__arm__) + std::cout << "ARM" << std::endl; +#else + std::cout << "unknown" << std::endl; +#endif + std::cout << std::endl; + + std::cout << "A server developed by " << STATUS_SERVER_DEVELOPERS << std::endl; + std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl; + std::cout << std::endl; + + // read global config + std::cout << ">> Loading config" << std::endl; + if (!g_config.load()) { + startupErrorMessage("Unable to load config.lua!"); + return; + } + +#ifdef _WIN32 + const std::string& defaultPriority = g_config.getString(ConfigManager::DEFAULT_PRIORITY); + if (strcasecmp(defaultPriority.c_str(), "high") == 0) { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + } else if (strcasecmp(defaultPriority.c_str(), "above-normal") == 0) { + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); + } +#endif + + //set RSA key + const char* p("14299623962416399520070177382898895550795403345466153217470516082934737582776038882967213386204600674145392845853859217990626450972452084065728686565928113"); + const char* q("7630979195970404721891201847792002125535401292779123937207447574596692788513647179235335529307251350570728407373705564708871762033017096809910315212884101"); + g_RSA.setKey(p, q); + + std::cout << ">> Establishing database connection..." << std::flush; + + Database* db = Database::getInstance(); + if (!db->connect()) { + startupErrorMessage("Failed to connect to database."); + return; + } + + std::cout << " MySQL " << Database::getClientVersion() << std::endl; + + // run database manager + std::cout << ">> Running database manager" << std::endl; + + if (!DatabaseManager::isDatabaseSetup()) { + startupErrorMessage("The database you have specified in config.lua is empty, please import the schema.sql to your database."); + return; + } + g_databaseTasks.start(); + + if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) { + std::cout << "> No tables were optimized." << std::endl; + } + + //load vocations + std::cout << ">> Loading vocations" << std::endl; + if (!g_vocations.loadFromXml()) { + startupErrorMessage("Unable to load vocations!"); + return; + } + + // load item data + std::cout << ">> Loading items" << std::endl; + if (!Item::items.loadItems()) { + startupErrorMessage("Unable to load items (SRV)!"); + return; + } + + std::cout << ">> Loading script systems" << std::endl; + if (!ScriptingManager::getInstance()->loadScriptSystems()) { + startupErrorMessage("Failed to load script systems"); + return; + } + + std::cout << ">> Loading monsters" << std::endl; + if (!g_monsters.loadFromXml()) { + startupErrorMessage("Unable to load monsters!"); + return; + } + + std::cout << ">> Checking world type... " << std::flush; + std::string worldType = asLowerCaseString(g_config.getString(ConfigManager::WORLD_TYPE)); + if (worldType == "pvp") { + g_game.setWorldType(WORLD_TYPE_PVP); + } else if (worldType == "no-pvp") { + g_game.setWorldType(WORLD_TYPE_NO_PVP); + } else if (worldType == "pvp-enforced") { + g_game.setWorldType(WORLD_TYPE_PVP_ENFORCED); + } else { + std::cout << std::endl; + + std::ostringstream ss; + ss << "> ERROR: Unknown world type: " << g_config.getString(ConfigManager::WORLD_TYPE) << ", valid world types are: pvp, no-pvp and pvp-enforced."; + startupErrorMessage(ss.str()); + return; + } + std::cout << asUpperCaseString(worldType) << std::endl; + + std::cout << ">> Loading map" << std::endl; + if (!g_game.loadMainMap(g_config.getString(ConfigManager::MAP_NAME))) { + startupErrorMessage("Failed to load map"); + return; + } + + std::cout << ">> Initializing gamestate" << std::endl; + g_game.setGameState(GAME_STATE_INIT); + + // Game client protocols + services->add(g_config.getNumber(ConfigManager::GAME_PORT)); + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + + // OT protocols + services->add(g_config.getNumber(ConfigManager::STATUS_PORT)); + + RentPeriod_t rentPeriod; + std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + + if (strRentPeriod == "yearly") { + rentPeriod = RENTPERIOD_YEARLY; + } else if (strRentPeriod == "weekly") { + rentPeriod = RENTPERIOD_WEEKLY; + } else if (strRentPeriod == "monthly") { + rentPeriod = RENTPERIOD_MONTHLY; + } else if (strRentPeriod == "daily") { + rentPeriod = RENTPERIOD_DAILY; + } else { + rentPeriod = RENTPERIOD_NEVER; + } + + g_game.map.houses.payHouses(rentPeriod); + + std::cout << ">> Loaded all modules, server starting up..." << std::endl; + +#ifndef _WIN32 + if (getuid() == 0 || geteuid() == 0) { + std::cout << "> Warning: " << STATUS_SERVER_NAME << " has been executed as root user, please consider running it as a normal user." << std::endl; + } +#endif + + g_game.start(services); + g_game.setGameState(GAME_STATE_NORMAL); + g_loaderSignal.notify_all(); +} diff --git a/src/outputmessage.cpp b/src/outputmessage.cpp new file mode 100644 index 0000000..75c1200 --- /dev/null +++ b/src/outputmessage.cpp @@ -0,0 +1,83 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "outputmessage.h" +#include "protocol.h" +#include "lockfree.h" +#include "scheduler.h" + +extern Scheduler g_scheduler; + +const uint16_t OUTPUTMESSAGE_FREE_LIST_CAPACITY = 2048; +const std::chrono::milliseconds OUTPUTMESSAGE_AUTOSEND_DELAY {10}; + +class OutputMessageAllocator +{ + public: + typedef OutputMessage value_type; + template + struct rebind {typedef LockfreePoolingAllocator other;}; +}; + +void OutputMessagePool::scheduleSendAll() +{ + auto functor = std::bind(&OutputMessagePool::sendAll, this); + g_scheduler.addEvent(createSchedulerTask(OUTPUTMESSAGE_AUTOSEND_DELAY.count(), functor)); +} + +void OutputMessagePool::sendAll() +{ + //dispatcher thread + for (auto& protocol : bufferedProtocols) { + auto& msg = protocol->getCurrentBuffer(); + if (msg) { + protocol->send(std::move(msg)); + } + } + + if (!bufferedProtocols.empty()) { + scheduleSendAll(); + } +} + +void OutputMessagePool::addProtocolToAutosend(Protocol_ptr protocol) +{ + //dispatcher thread + if (bufferedProtocols.empty()) { + scheduleSendAll(); + } + bufferedProtocols.emplace_back(protocol); +} + +void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) +{ + //dispatcher thread + auto it = std::find(bufferedProtocols.begin(), bufferedProtocols.end(), protocol); + if (it != bufferedProtocols.end()) { + std::swap(*it, bufferedProtocols.back()); + bufferedProtocols.pop_back(); + } +} + +OutputMessage_ptr OutputMessagePool::getOutputMessage() +{ + return std::allocate_shared(OutputMessageAllocator()); +} diff --git a/src/outputmessage.h b/src/outputmessage.h new file mode 100644 index 0000000..52526c5 --- /dev/null +++ b/src/outputmessage.h @@ -0,0 +1,104 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 +#define FS_OUTPUTMESSAGE_H_C06AAED85C7A43939F22D229297C0CC1 + +#include "networkmessage.h" +#include "connection.h" +#include "tools.h" + +class Protocol; + +class OutputMessage : public NetworkMessage +{ + public: + OutputMessage() = default; + + // non-copyable + OutputMessage(const OutputMessage&) = delete; + OutputMessage& operator=(const OutputMessage&) = delete; + + uint8_t* getOutputBuffer() { + return buffer + outputBufferStart; + } + + void writeMessageLength() { + add_header(info.length); + } + + void addCryptoHeader() { + writeMessageLength(); + } + + inline void append(const NetworkMessage& msg) { + auto msgLen = msg.getLength(); + memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen); + info.length += msgLen; + info.position += msgLen; + } + + inline void append(const OutputMessage_ptr& msg) { + auto msgLen = msg->getLength(); + memcpy(buffer + info.position, msg->getBuffer() + 4, msgLen); + info.length += msgLen; + info.position += msgLen; + } + + protected: + template + inline void add_header(T add) { + assert(outputBufferStart >= sizeof(T)); + outputBufferStart -= sizeof(T); + memcpy(buffer + outputBufferStart, &add, sizeof(T)); + //added header size to the message size + info.length += sizeof(T); + } + + MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; +}; + +class OutputMessagePool +{ + public: + // non-copyable + OutputMessagePool(const OutputMessagePool&) = delete; + OutputMessagePool& operator=(const OutputMessagePool&) = delete; + + static OutputMessagePool& getInstance() { + static OutputMessagePool instance; + return instance; + } + + void sendAll(); + void scheduleSendAll(); + + static OutputMessage_ptr getOutputMessage(); + + void addProtocolToAutosend(Protocol_ptr protocol); + void removeProtocolFromAutosend(const Protocol_ptr& protocol); + private: + OutputMessagePool() = default; + //NOTE: A vector is used here because this container is mostly read + //and relatively rarely modified (only when a client connects/disconnects) + std::vector bufferedProtocols; +}; + + +#endif diff --git a/src/party.cpp b/src/party.cpp new file mode 100644 index 0000000..e778572 --- /dev/null +++ b/src/party.cpp @@ -0,0 +1,458 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "party.h" +#include "game.h" +#include "configmanager.h" + +extern Game g_game; +extern ConfigManager g_config; + +Party::Party(Player* leader) : leader(leader) +{ + leader->setParty(this); +} + +void Party::disband() +{ + Player* currentLeader = leader; + leader = nullptr; + + currentLeader->setParty(nullptr); + currentLeader->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(currentLeader); + currentLeader->sendCreatureSkull(currentLeader); + currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); + + for (Player* invitee : inviteList) { + invitee->removePartyInvitation(this); + currentLeader->sendCreatureShield(invitee); + } + inviteList.clear(); + + for (Player* member : memberList) { + member->setParty(nullptr); + member->sendClosePrivate(CHANNEL_PARTY); + member->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); + } + + for (Player* member : memberList) { + g_game.updatePlayerShield(member); + + for (Player* otherMember : memberList) { + otherMember->sendCreatureSkull(member); + } + + member->sendCreatureSkull(currentLeader); + currentLeader->sendCreatureSkull(member); + } + + memberList.clear(); + delete this; +} + +bool Party::leaveParty(Player* player) +{ + if (!player) { + return false; + } + + if (player->getParty() != this && leader != player) { + return false; + } + + bool missingLeader = false; + if (leader == player) { + if (!memberList.empty()) { + if (memberList.size() == 1 && inviteList.empty()) { + missingLeader = true; + } else { + passPartyLeadership(memberList.front()); + } + } else { + missingLeader = true; + } + } + + //since we already passed the leadership, we remove the player from the list + auto it = std::find(memberList.begin(), memberList.end(), player); + if (it != memberList.end()) { + memberList.erase(it); + } + + player->setParty(nullptr); + player->sendClosePrivate(CHANNEL_PARTY); + g_game.updatePlayerShield(player); + + for (Player* member : memberList) { + member->sendCreatureSkull(player); + player->sendPlayerPartyIcons(member); + } + + leader->sendCreatureSkull(player); + player->sendCreatureSkull(player); + player->sendPlayerPartyIcons(leader); + + player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party."); + + updateSharedExperience(); + updateVocationsList(); + + clearPlayerPoints(player); + + std::ostringstream ss; + ss << player->getName() << " has left the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); + + if (missingLeader || empty()) { + disband(); + } + + return true; +} + +bool Party::passPartyLeadership(Player* player) +{ + if (!player || leader == player || player->getParty() != this) { + return false; + } + + //Remove it before to broadcast the message correctly + auto it = std::find(memberList.begin(), memberList.end(), player); + if (it != memberList.end()) { + memberList.erase(it); + } + + std::ostringstream ss; + ss << player->getName() << " is now the leader of the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str(), true); + + Player* oldLeader = leader; + leader = player; + + memberList.insert(memberList.begin(), oldLeader); + + updateSharedExperience(); + + for (Player* member : memberList) { + member->sendCreatureShield(oldLeader); + member->sendCreatureShield(leader); + } + + for (Player* invitee : inviteList) { + invitee->sendCreatureShield(oldLeader); + invitee->sendCreatureShield(leader); + } + + leader->sendCreatureShield(oldLeader); + leader->sendCreatureShield(leader); + + player->sendTextMessage(MESSAGE_INFO_DESCR, "You are now the leader of the party."); + return true; +} + +bool Party::joinParty(Player& player) +{ + auto it = std::find(inviteList.begin(), inviteList.end(), &player); + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + std::ostringstream ss; + ss << player.getName() << " has joined the party."; + broadcastPartyMessage(MESSAGE_INFO_DESCR, ss.str()); + + player.setParty(this); + + g_game.updatePlayerShield(&player); + + for (Player* member : memberList) { + member->sendCreatureSkull(&player); + player.sendPlayerPartyIcons(member); + } + + player.sendCreatureSkull(&player); + leader->sendCreatureSkull(&player); + player.sendPlayerPartyIcons(leader); + + memberList.push_back(&player); + + player.removePartyInvitation(this); + updateSharedExperience(); + updateVocationsList(); + + const std::string& leaderName = leader->getName(); + ss.str(std::string()); + ss << "You have joined " << leaderName << "'" << (leaderName.back() == 's' ? "" : "s") << + " party. Open the party channel to communicate with your companions."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return true; +} + +bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) +{ + auto it = std::find(inviteList.begin(), inviteList.end(), &player); + if (it == inviteList.end()) { + return false; + } + + inviteList.erase(it); + + leader->sendCreatureShield(&player); + player.sendCreatureShield(leader); + + if (removeFromPlayer) { + player.removePartyInvitation(this); + } + + if (empty()) { + disband(); + } + + return true; +} + +void Party::revokeInvitation(Player& player) +{ + std::ostringstream ss; + ss << leader->getName() << " has revoked " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " invitation."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + ss.str(std::string()); + ss << "Invitation for " << player.getName() << " has been revoked."; + leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + removeInvite(player); +} + +bool Party::invitePlayer(Player& player) +{ + if (isPlayerInvited(&player)) { + return false; + } + + std::ostringstream ss; + ss << player.getName() << " has been invited."; + + if (memberList.empty() && inviteList.empty()) { + ss << " Open the party channel to communicate with your members."; + g_game.updatePlayerShield(leader); + leader->sendCreatureSkull(leader); + } + + leader->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + inviteList.push_back(&player); + + leader->sendCreatureShield(&player); + player.sendCreatureShield(leader); + + player.addPartyInvitation(this); + + ss.str(std::string()); + ss << leader->getName() << " has invited you to " << (leader->getSex() == PLAYERSEX_FEMALE ? "her" : "his") << " party."; + player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + return true; +} + +bool Party::isPlayerInvited(const Player* player) const +{ + return std::find(inviteList.begin(), inviteList.end(), player) != inviteList.end(); +} + +void Party::updateAllPartyIcons() +{ + for (Player* member : memberList) { + for (Player* otherMember : memberList) { + member->sendCreatureShield(otherMember); + } + + member->sendCreatureShield(leader); + leader->sendCreatureShield(member); + } + leader->sendCreatureShield(leader); +} + +void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations /*= false*/) +{ + for (Player* member : memberList) { + member->sendTextMessage(msgClass, msg); + } + + leader->sendTextMessage(msgClass, msg); + + if (sendToInvitations) { + for (Player* invitee : inviteList) { + invitee->sendTextMessage(msgClass, msg); + } + } +} + +void Party::broadcastPartyLoot(const std::string& loot) +{ + leader->sendTextMessage(MESSAGE_INFO_DESCR, loot); + + for (Player* member : memberList) { + member->sendTextMessage(MESSAGE_INFO_DESCR, loot); + } +} + +void Party::updateSharedExperience() +{ + if (sharedExpActive) { + bool result = canEnableSharedExperience(); + if (result != sharedExpEnabled) { + sharedExpEnabled = result; + updateAllPartyIcons(); + } + } +} + +void Party::updateVocationsList() +{ + std::set vocationIds; + + uint32_t vocationId = leader->getVocation()->getFromVocation(); + if (vocationId != VOCATION_NONE) { + vocationIds.insert(vocationId); + } + + for (const Player* member : memberList) { + vocationId = member->getVocation()->getFromVocation(); + if (vocationId != VOCATION_NONE) { + vocationIds.insert(vocationId); + } + } + + size_t size = vocationIds.size(); + if (size > 1) { + extraExpRate = static_cast(size * (10 + (size - 1) * 5)) / 100.f; + } else { + extraExpRate = 0.20f; + } +} + +bool Party::setSharedExperience(Player* player, bool sharedExpActive) +{ + if (!player || leader != player) { + return false; + } + + if (this->sharedExpActive == sharedExpActive) { + return true; + } + + this->sharedExpActive = sharedExpActive; + + if (sharedExpActive) { + this->sharedExpEnabled = canEnableSharedExperience(); + + if (this->sharedExpEnabled) { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience is now active."); + } else { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been activated, but some members of your party are inactive."); + } + } else { + leader->sendTextMessage(MESSAGE_INFO_DESCR, "Shared Experience has been deactivated."); + } + + updateAllPartyIcons(); + return true; +} + +void Party::shareExperience(uint64_t experience) +{ + uint64_t shareExperience = static_cast(std::ceil((static_cast(experience) * (extraExpRate + 1)) / (memberList.size() + 1))); + for (Player* member : memberList) { + member->onGainSharedExperience(shareExperience); + } + leader->onGainSharedExperience(shareExperience); +} + +bool Party::canUseSharedExperience(const Player* player) const +{ + if (memberList.empty()) { + return false; + } + + uint32_t highestLevel = leader->getLevel(); + for (Player* member : memberList) { + if (member->getLevel() > highestLevel) { + highestLevel = member->getLevel(); + } + } + + uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); + if (player->getLevel() < minLevel) { + return false; + } + + if (!Position::areInRange<30, 30, 1>(leader->getPosition(), player->getPosition())) { + return false; + } + + if (!player->hasFlag(PlayerFlag_NotGainInFight)) { + //check if the player has healed/attacked anything recently + auto it = ticksMap.find(player->getID()); + if (it == ticksMap.end()) { + return false; + } + + uint64_t timeDiff = OTSYS_TIME() - it->second; + if (timeDiff > static_cast(g_config.getNumber(ConfigManager::PZ_LOCKED))) { + return false; + } + } + return true; +} + +bool Party::canEnableSharedExperience() +{ + if (!canUseSharedExperience(leader)) { + return false; + } + + for (Player* member : memberList) { + if (!canUseSharedExperience(member)) { + return false; + } + } + return true; +} + +void Party::updatePlayerTicks(Player* player, uint32_t points) +{ + if (points != 0 && !player->hasFlag(PlayerFlag_NotGainInFight)) { + ticksMap[player->getID()] = OTSYS_TIME(); + updateSharedExperience(); + } +} + +void Party::clearPlayerPoints(Player* player) +{ + auto it = ticksMap.find(player->getID()); + if (it != ticksMap.end()) { + ticksMap.erase(it); + updateSharedExperience(); + } +} diff --git a/src/party.h b/src/party.h new file mode 100644 index 0000000..baccc56 --- /dev/null +++ b/src/party.h @@ -0,0 +1,101 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 +#define FS_PARTY_H_41D4D7CF417C4CC99FAE94D552255044 + +#include "player.h" +#include "monsters.h" + +class Player; +class Party; + +typedef std::vector PlayerVector; + +class Party +{ + public: + explicit Party(Player* leader); + + Player* getLeader() const { + return leader; + } + PlayerVector& getMembers() { + return memberList; + } + const PlayerVector& getInvitees() const { + return inviteList; + } + size_t getMemberCount() const { + return memberList.size(); + } + size_t getInvitationCount() const { + return inviteList.size(); + } + + void disband(); + bool invitePlayer(Player& player); + bool joinParty(Player& player); + void revokeInvitation(Player& player); + bool passPartyLeadership(Player* player); + bool leaveParty(Player* player); + + bool removeInvite(Player& player, bool removeFromPlayer = true); + + bool isPlayerInvited(const Player* player) const; + void updateAllPartyIcons(); + void broadcastPartyMessage(MessageClasses msgClass, const std::string& msg, bool sendToInvitations = false); + void broadcastPartyLoot(const std::string& loot); + bool empty() const { + return memberList.empty() && inviteList.empty(); + } + + void shareExperience(uint64_t experience); + bool setSharedExperience(Player* player, bool sharedExpActive); + bool isSharedExperienceActive() const { + return sharedExpActive; + } + bool isSharedExperienceEnabled() const { + return sharedExpEnabled; + } + bool canUseSharedExperience(const Player* player) const; + void updateSharedExperience(); + + void updateVocationsList(); + + void updatePlayerTicks(Player* player, uint32_t points); + void clearPlayerPoints(Player* player); + + protected: + bool canEnableSharedExperience(); + + std::map ticksMap; + + PlayerVector memberList; + PlayerVector inviteList; + + Player* leader; + + float extraExpRate = 0.20f; + + bool sharedExpActive = false; + bool sharedExpEnabled = false; +}; + +#endif diff --git a/src/player.cpp b/src/player.cpp new file mode 100644 index 0000000..bd2dab8 --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,3665 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "bed.h" +#include "chat.h" +#include "combat.h" +#include "configmanager.h" +#include "creatureevent.h" +#include "game.h" +#include "iologindata.h" +#include "monster.h" +#include "movement.h" +#include "scheduler.h" + +extern ConfigManager g_config; +extern Game g_game; +extern Chat* g_chat; +extern Vocations g_vocations; +extern MoveEvents* g_moveEvents; +extern CreatureEvents* g_creatureEvents; + +MuteCountMap Player::muteCountMap; + +uint32_t Player::playerAutoID = 0x10000000; + +Player::Player(ProtocolGame_ptr p) : + Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) +{ +} + +Player::~Player() +{ + for (Item* item : inventory) { + if (item) { + item->setParent(nullptr); + item->decrementReferenceCounter(); + } + } + + for (const auto& it : depotLockerMap) { + it.second->decrementReferenceCounter(); + } + + setWriteItem(nullptr); + setEditHouse(nullptr); +} + +bool Player::setVocation(uint16_t vocId) +{ + Vocation* voc = g_vocations.getVocation(vocId); + if (!voc) { + return false; + } + vocation = voc; + + Condition* condition = getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); + if (condition) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, vocation->getHealthGainAmount()); + condition->setParam(CONDITION_PARAM_HEALTHTICKS, vocation->getHealthGainTicks() * 1000); + condition->setParam(CONDITION_PARAM_MANAGAIN, vocation->getManaGainAmount()); + condition->setParam(CONDITION_PARAM_MANATICKS, vocation->getManaGainTicks() * 1000); + } + return true; +} + +bool Player::isPushable() const +{ + if (hasFlag(PlayerFlag_CannotBePushed)) { + return false; + } + return Creature::isPushable(); +} + +std::string Player::getDescription(int32_t lookDistance) const +{ + std::ostringstream s; + + if (lookDistance == -1) { + s << "yourself."; + + if (group->access) { + s << " You are " << group->name << '.'; + } else if (vocation->getId() != VOCATION_NONE) { + s << " You are " << vocation->getVocDescription() << '.'; + } else { + s << " You have no vocation."; + } + } else { + s << name; + if (!group->access) { + s << " (Level " << level << ')'; + } + s << '.'; + + if (sex == PLAYERSEX_FEMALE) { + s << " She"; + } else { + s << " He"; + } + + if (group->access) { + s << " is " << group->name << '.'; + } else if (vocation->getId() != VOCATION_NONE) { + s << " is " << vocation->getVocDescription() << '.'; + } else { + s << " has no vocation."; + } + } + + if (guild && guildRank) { + if (lookDistance == -1) { + s << " You are "; + } else if (sex == PLAYERSEX_FEMALE) { + s << " She is "; + } else { + s << " He is "; + } + + s << guildRank->name << " of the " << guild->getName(); + if (!guildNick.empty()) { + s << " (" << guildNick << ')'; + } + + s << "."; + } + + return s.str(); +} + +Item* Player::getInventoryItem(slots_t slot) const +{ + if (slot < CONST_SLOT_FIRST || slot > CONST_SLOT_LAST) { + return nullptr; + } + return inventory[slot]; +} + +void Player::addConditionSuppressions(uint32_t conditions) +{ + conditionSuppressions |= conditions; +} + +void Player::removeConditionSuppressions(uint32_t conditions) +{ + conditionSuppressions &= ~conditions; +} + +Item* Player::getWeapon() const +{ + Item* item = inventory[CONST_SLOT_LEFT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + return item; + } + + item = inventory[CONST_SLOT_RIGHT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + return item; + } + + return nullptr; +} + +Item* Player::getAmmunition() const +{ + return inventory[CONST_SLOT_AMMO]; +} + +int32_t Player::getArmor() const +{ + int32_t armor = 0; // base armor + + static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; + for (slots_t slot : armorSlots) { + Item* inventoryItem = inventory[slot]; + if (inventoryItem) { + armor += inventoryItem->getArmor(); + } + } + + if (armor > 1) { + armor = rand() % (armor >> 1) + (armor >> 1); + } + + return armor; +} + +void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const +{ + shield = nullptr; + weapon = nullptr; + + for (uint32_t slot = CONST_SLOT_RIGHT; slot <= CONST_SLOT_LEFT; slot++) { + Item* item = inventory[slot]; + if (!item) { + continue; + } + + switch (item->getWeaponType()) { + case WEAPON_NONE: + break; + + case WEAPON_SHIELD: { + if (!shield || item->getDefense() > shield->getDefense()) { + shield = item; + } + break; + } + + default: { // weapons that are not shields + weapon = item; + break; + } + } + } +} + +int32_t Player::getDefense() +{ + int32_t totalDefense = 5; + int32_t defenseSkill = getSkillLevel(SKILL_FIST); + + const Item* weapon; + const Item* shield; + getShieldAndWeapon(shield, weapon); + + if (weapon) { + totalDefense = weapon->getDefense() + 1; + + switch (weapon->getWeaponType()) { + case WEAPON_AXE: + defenseSkill = SKILL_AXE; + break; + case WEAPON_SWORD: + defenseSkill = SKILL_SWORD; + break; + case WEAPON_CLUB: + defenseSkill = SKILL_CLUB; + break; + case WEAPON_DISTANCE: + case WEAPON_AMMO: + defenseSkill = SKILL_DISTANCE; + break; + default: + break; + } + } + + if (shield) { + totalDefense = shield->getDefense() + 1; + defenseSkill = getSkillLevel(SKILL_SHIELD); + } + + fightMode_t attackMode = getFightMode(); + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; +} + +fightMode_t Player::getFightMode() const +{ + return fightMode; +} + +uint16_t Player::getClientIcons() const +{ + uint16_t icons = 0; + for (Condition* condition : conditions) { + if (!isSuppress(condition->getType())) { + icons |= condition->getIcons(); + } + } + + // Game client debugs with 10 or more icons + // so let's prevent that from happening. + std::bitset<20> icon_bitset(static_cast(icons)); + for (size_t pos = 0, bits_set = icon_bitset.count(); bits_set >= 10; ++pos) { + if (icon_bitset[pos]) { + icon_bitset.reset(pos); + --bits_set; + } + } + return icon_bitset.to_ulong(); +} + +void Player::updateInventoryWeight() +{ + if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return; + } + + inventoryWeight = 0; + for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + const Item* item = inventory[i]; + if (item) { + inventoryWeight += item->getWeight(); + } + } +} + +void Player::addSkillAdvance(skills_t skill, uint64_t count) +{ + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + //player has reached max skill + return; + } + + if (skill == SKILL_MAGLEVEL) { + count *= g_config.getNumber(g_config.RATE_MAGIC); + } else { + count *= g_config.getNumber(g_config.RATE_SKILL); + } + + if (count == 0) { + return; + } + + bool sendUpdateSkills = false; + while ((skills[skill].tries + count) >= nextReqTries) { + count -= nextReqTries - skills[skill].tries; + skills[skill].level++; + skills[skill].tries = 0; + skills[skill].percent = 0; + + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); + + sendUpdateSkills = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + count = 0; + break; + } + } + + skills[skill].tries += count; + + uint32_t newPercent; + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); + } else { + newPercent = 0; + } + + if (skills[skill].percent != newPercent) { + skills[skill].percent = newPercent; + sendUpdateSkills = true; + } + + if (sendUpdateSkills) { + sendSkills(); + } +} + +void Player::setVarStats(stats_t stat, int32_t modifier) +{ + varStats[stat] += modifier; + + switch (stat) { + case STAT_MAXHITPOINTS: { + if (getHealth() > getMaxHealth()) { + Creature::changeHealth(getMaxHealth() - getHealth()); + } else { + g_game.addCreatureHealth(this); + } + break; + } + + case STAT_MAXMANAPOINTS: { + if (getMana() > getMaxMana()) { + Creature::changeMana(getMaxMana() - getMana()); + } + break; + } + + default: { + break; + } + } +} + +int32_t Player::getDefaultStats(stats_t stat) const +{ + switch (stat) { + case STAT_MAXHITPOINTS: return healthMax; + case STAT_MAXMANAPOINTS: return manaMax; + case STAT_MAGICPOINTS: return getBaseMagicLevel(); + default: return 0; + } +} + +void Player::addContainer(uint8_t cid, Container* container) +{ + if (cid > 0xF) { + return; + } + + auto it = openContainers.find(cid); + if (it != openContainers.end()) { + OpenContainer& openContainer = it->second; + openContainer.container = container; + openContainer.index = 0; + } else { + OpenContainer openContainer; + openContainer.container = container; + openContainer.index = 0; + openContainers[cid] = openContainer; + } +} + +void Player::closeContainer(uint8_t cid) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return; + } + + openContainers.erase(it); +} + +void Player::setContainerIndex(uint8_t cid, uint16_t index) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return; + } + it->second.index = index; +} + +Container* Player::getContainerByID(uint8_t cid) +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return nullptr; + } + return it->second.container; +} + +int8_t Player::getContainerID(const Container* container) const +{ + for (const auto& it : openContainers) { + if (it.second.container == container) { + return it.first; + } + } + return -1; +} + +uint16_t Player::getContainerIndex(uint8_t cid) const +{ + auto it = openContainers.find(cid); + if (it == openContainers.end()) { + return 0; + } + return it->second.index; +} + +uint16_t Player::getLookCorpse() const +{ + if (sex == PLAYERSEX_FEMALE) { + return ITEM_FEMALE_CORPSE; + } else { + return ITEM_MALE_CORPSE; + } +} + +void Player::addStorageValue(const uint32_t key, const int32_t value) +{ + if (value != -1) { + storageMap[key] = value; + } else { + storageMap.erase(key); + } +} + +bool Player::getStorageValue(const uint32_t key, int32_t& value) const +{ + auto it = storageMap.find(key); + if (it == storageMap.end()) { + value = 0; + return false; + } + + value = it->second; + return true; +} + +bool Player::canSee(const Position& pos) const +{ + if (!client) { + return false; + } + return client->canSee(pos); +} + +bool Player::canSeeCreature(const Creature* creature) const +{ + if (creature == this) { + return true; + } + + if (creature->isInGhostMode() && !group->access) { + return false; + } + + if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) { + return false; + } + return true; +} + +void Player::onReceiveMail() const +{ + if (isNearDepotBox()) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived."); + } +} + +bool Player::isNearDepotBox() const +{ + const Position& pos = getPosition(); + for (int32_t cx = -1; cx <= 1; ++cx) { + for (int32_t cy = -1; cy <= 1; ++cy) { + Tile* tile = g_game.map.getTile(pos.x + cx, pos.y + cy, pos.z); + if (!tile) { + continue; + } + + if (tile->hasFlag(TILESTATE_DEPOT)) { + return true; + } + } + } + return false; +} + +DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate) +{ + auto it = depotLockerMap.find(depotId); + if (it != depotLockerMap.end()) { + return it->second; + } + + if (autoCreate) { + DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); + depotLocker->setDepotId(depotId); + Item* depotItem = Item::CreateItem(ITEM_DEPOT); + if (depotItem) { + depotLocker->internalAddThing(depotItem); + } + depotLockerMap[depotId] = depotLocker; + return depotLocker; + } + + return nullptr; +} + +void Player::sendCancelMessage(ReturnValue message) const +{ + sendCancelMessage(getReturnMessage(message)); +} + +void Player::sendStats() +{ + if (client) { + client->sendStats(); + } +} + +void Player::sendPing() +{ + int64_t timeNow = OTSYS_TIME(); + + bool hasLostConnection = false; + if ((timeNow - lastPing) >= 5000) { + lastPing = timeNow; + if (client) { + client->sendPing(); + } else { + hasLostConnection = true; + } + } + + int64_t noPongTime = timeNow - lastPong; + if ((hasLostConnection || noPongTime >= 7000) && attackedCreature && attackedCreature->getPlayer()) { + setAttackedCreature(nullptr); + } + + if (noPongTime >= 60000 && canLogout()) { + if (g_creatureEvents->playerLogout(this)) { + if (client) { + client->logout(true, true); + } else { + g_game.removeCreature(this, true); + } + } + } +} + +Item* Player::getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen) +{ + windowTextId = this->windowTextId; + maxWriteLen = this->maxWriteLen; + return writeItem; +} + +void Player::setWriteItem(Item* item, uint16_t maxWriteLen /*= 0*/) +{ + windowTextId++; + + if (writeItem) { + writeItem->decrementReferenceCounter(); + } + + if (item) { + writeItem = item; + this->maxWriteLen = maxWriteLen; + writeItem->incrementReferenceCounter(); + } else { + writeItem = nullptr; + this->maxWriteLen = 0; + } +} + +House* Player::getEditHouse(uint32_t& windowTextId, uint32_t& listId) +{ + windowTextId = this->windowTextId; + listId = this->editListId; + return editHouse; +} + +void Player::setEditHouse(House* house, uint32_t listId /*= 0*/) +{ + windowTextId++; + editHouse = house; + editListId = listId; +} + +void Player::sendHouseWindow(House* house, uint32_t listId) const +{ + if (!client) { + return; + } + + std::string text; + if (house->getAccessList(listId, text)) { + client->sendHouseWindow(windowTextId, text); + } +} + +//container +void Player::sendAddContainerItem(const Container* container, const Item* item) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + if (openContainer.index >= container->capacity()) { + item = container->getItemByIndex(openContainer.index - 1); + } + client->sendAddContainerItem(it.first, item); + } +} + +void Player::sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + if (slot < openContainer.index) { + continue; + } + + uint16_t pageEnd = openContainer.index + container->capacity(); + if (slot >= pageEnd) { + continue; + } + + client->sendUpdateContainerItem(it.first, slot, newItem); + } +} + +void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) +{ + if (!client) { + return; + } + + for (auto& it : openContainers) { + OpenContainer& openContainer = it.second; + if (openContainer.container != container) { + continue; + } + + uint16_t& firstIndex = openContainer.index; + if (firstIndex > 0 && firstIndex >= container->size() - 1) { + firstIndex -= container->capacity(); + sendContainer(it.first, container, false, firstIndex); + } + + client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex)); + } +} + +void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) +{ + Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); + + if (oldItem != newItem) { + onRemoveTileItem(tile, pos, oldType, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + if (tradeItem && oldItem == tradeItem) { + g_game.internalCloseTrade(this); + } + } +} + +void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) +{ + Creature::onRemoveTileItem(tile, pos, iType, item); + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCreatureAppear(Creature* creature, bool isLogin) +{ + Creature::onCreatureAppear(creature, isLogin); + + if (isLogin && creature == this) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item) { + item->startDecaying(); + g_moveEvents->onPlayerEquip(this, item, static_cast(slot), false); + } + } + + for (Condition* condition : storedConditionList) { + addCondition(condition); + } + storedConditionList.clear(); + + BedItem* bed = g_game.getBedBySleeper(guid); + if (bed) { + bed->wakeUp(this); + } + + std::cout << name << " has logged in." << std::endl; + + if (guild) { + guild->addMember(this); + } + + int32_t offlineTime; + if (getLastLogout() != 0) { + // Not counting more than 21 days to prevent overflow when multiplying with 1000 (for milliseconds). + offlineTime = std::min(time(nullptr) - getLastLogout(), 86400 * 21); + } else { + offlineTime = 0; + } + + for (Condition* condition : getMuteConditions()) { + condition->setTicks(condition->getTicks() - (offlineTime * 1000)); + if (condition->getTicks() <= 0) { + removeCondition(condition); + } + } + + g_game.checkPlayersRecord(); + IOLoginData::updateOnlineStatus(guid, true); + } +} + +void Player::onAttackedCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); + } +} + +void Player::onFollowCreatureDisappear(bool isLogout) +{ + sendCancelTarget(); + + if (!isLogout) { + sendTextMessage(MESSAGE_STATUS_SMALL, "Target lost."); + } +} + +void Player::onChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (attackedCreature && !hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + + sendIcons(); +} + +void Player::onAttackedCreatureChangeZone(ZoneType_t zone) +{ + if (zone == ZONE_PROTECTION) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } else if (zone == ZONE_NOPVP) { + if (attackedCreature->getPlayer()) { + if (!hasFlag(PlayerFlag_IgnoreProtectionZone)) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + } else if (zone == ZONE_NORMAL) { + //attackedCreature can leave a pvp zone if not pzlocked + if (g_game.getWorldType() == WORLD_TYPE_NO_PVP) { + if (attackedCreature->getPlayer()) { + setAttackedCreature(nullptr); + onAttackedCreatureDisappear(false); + } + } + } +} + +void Player::onRemoveCreature(Creature* creature, bool isLogout) +{ + Creature::onRemoveCreature(creature, isLogout); + + if (creature == this) { + if (isLogout) { + loginPosition = getPosition(); + } + + lastLogout = time(nullptr); + + if (eventWalk != 0) { + setFollowCreature(nullptr); + } + + if (tradePartner) { + g_game.internalCloseTrade(this); + } + + clearPartyInvitations(); + + if (party) { + party->leaveParty(this); + } + + g_chat->removeUserFromAllChannels(*this); + + std::cout << getName() << " has logged out." << std::endl; + + if (guild) { + guild->removeMember(this); + } + + IOLoginData::updateOnlineStatus(guid, false); + + bool saved = false; + for (uint32_t tries = 0; tries < 3; ++tries) { + if (IOLoginData::savePlayer(this)) { + saved = true; + break; + } + } + + if (!saved) { + std::cout << "Error while saving player: " << getName() << std::endl; + } + } +} + +void Player::onWalk(Direction& dir) +{ + Creature::onWalk(dir); + setNextActionTask(nullptr); + setNextAction(OTSYS_TIME() + getStepDuration(dir)); +} + +void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) +{ + Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); + + if (hasFollowPath && (creature == followCreature || (creature == this && followCreature))) { + isUpdatingPath = false; + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + } + + if (creature != this) { + return; + } + + if (tradeState != TRADE_TRANSFER) { + //check if we should close trade + if (tradeItem && !Position::areInRange<1, 1, 0>(tradeItem->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + + if (tradePartner && !Position::areInRange<2, 2, 0>(tradePartner->getPosition(), getPosition())) { + g_game.internalCloseTrade(this); + } + } + + if (party) { + party->updateSharedExperience(); + } + + if (teleport || oldPos.z != newPos.z) { + int32_t ticks = g_config.getNumber(ConfigManager::STAIRHOP_DELAY); + if (ticks > 0) { + if (Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_PACIFIED, ticks, 0)) { + addCondition(condition); + } + } + } +} + +//container +void Player::onAddContainerItem(const Item* item) +{ + checkTradeState(item); +} + +void Player::onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem) +{ + if (oldItem != newItem) { + onRemoveContainerItem(container, oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveContainerItem(const Container* container, const Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + if (tradeItem->getParent() != container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::onCloseContainer(const Container* container) +{ + if (!client) { + return; + } + + for (const auto& it : openContainers) { + if (it.second.container == container) { + client->sendCloseContainer(it.first); + } + } +} + +void Player::onSendContainer(const Container* container) +{ + if (!client) { + return; + } + + bool hasParent = container->hasParent(); + for (const auto& it : openContainers) { + const OpenContainer& openContainer = it.second; + if (openContainer.container == container) { + client->sendContainer(it.first, container, hasParent, openContainer.index); + } + } +} + +//inventory +void Player::onUpdateInventoryItem(Item* oldItem, Item* newItem) +{ + if (oldItem != newItem) { + onRemoveInventoryItem(oldItem); + } + + if (tradeState != TRADE_TRANSFER) { + checkTradeState(oldItem); + } +} + +void Player::onRemoveInventoryItem(Item* item) +{ + if (tradeState != TRADE_TRANSFER) { + checkTradeState(item); + + if (tradeItem) { + const Container* container = item->getContainer(); + if (container && container->isHoldingItem(tradeItem)) { + g_game.internalCloseTrade(this); + } + } + } +} + +void Player::checkTradeState(const Item* item) +{ + if (!tradeItem || tradeState == TRADE_TRANSFER) { + return; + } + + if (tradeItem == item) { + g_game.internalCloseTrade(this); + } else { + const Container* container = dynamic_cast(item->getParent()); + while (container) { + if (container == tradeItem) { + g_game.internalCloseTrade(this); + break; + } + + container = dynamic_cast(container->getParent()); + } + } +} + +void Player::setNextWalkActionTask(SchedulerTask* task) +{ + if (walkTaskEvent != 0) { + g_scheduler.stopEvent(walkTaskEvent); + walkTaskEvent = 0; + } + + delete walkTask; + walkTask = task; +} + +void Player::setNextWalkTask(SchedulerTask* task) +{ + if (nextStepEvent != 0) { + g_scheduler.stopEvent(nextStepEvent); + nextStepEvent = 0; + } + + if (task) { + nextStepEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +void Player::setNextActionTask(SchedulerTask* task) +{ + if (actionTaskEvent != 0) { + g_scheduler.stopEvent(actionTaskEvent); + actionTaskEvent = 0; + } + + if (task) { + actionTaskEvent = g_scheduler.addEvent(task); + resetIdleTime(); + } +} + +uint32_t Player::getNextActionTime() const +{ + return std::max(SCHEDULER_MINTICKS, nextAction - OTSYS_TIME()); +} + +void Player::onThink(uint32_t interval) +{ + Creature::onThink(interval); + + sendPing(); + + MessageBufferTicks += interval; + if (MessageBufferTicks >= 1500) { + MessageBufferTicks = 0; + addMessageBuffer(); + } + + if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) { + idleTime += interval; + const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES); + if ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) { + kickPlayer(true); + } else if (client && idleTime == 60000 * kickAfterMinutes) { + std::ostringstream ss; + ss << "You have been idle for " << kickAfterMinutes << " minutes. You will be disconnected in one minute if you are still idle then."; + client->sendTextMessage(TextMessage(MESSAGE_STATUS_WARNING, ss.str())); + } + } + + if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + checkSkullTicks(); + } +} + +uint32_t Player::isMuted() const +{ + if (hasFlag(PlayerFlag_CannotBeMuted)) { + return 0; + } + + int32_t muteTicks = 0; + for (Condition* condition : conditions) { + if (condition->getType() == CONDITION_MUTED && condition->getTicks() > muteTicks) { + muteTicks = condition->getTicks(); + } + } + return static_cast(muteTicks) / 1000; +} + +void Player::addMessageBuffer() +{ + if (MessageBufferCount > 0 && g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER) != 0 && !hasFlag(PlayerFlag_CannotBeMuted)) { + --MessageBufferCount; + } +} + +void Player::removeMessageBuffer() +{ + if (hasFlag(PlayerFlag_CannotBeMuted)) { + return; + } + + const int32_t maxMessageBuffer = g_config.getNumber(ConfigManager::MAX_MESSAGEBUFFER); + if (maxMessageBuffer != 0 && MessageBufferCount <= maxMessageBuffer + 1) { + if (++MessageBufferCount > maxMessageBuffer) { + uint32_t muteCount = 1; + auto it = muteCountMap.find(guid); + if (it != muteCountMap.end()) { + muteCount = it->second; + } + + uint32_t muteTime = 5 * muteCount * muteCount; + muteCountMap[guid] = muteCount + 1; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_MUTED, muteTime * 1000, 0); + addCondition(condition); + + std::ostringstream ss; + ss << "You are muted for " << muteTime << " seconds."; + sendTextMessage(MESSAGE_STATUS_SMALL, ss.str()); + } + } +} + +void Player::drainHealth(Creature* attacker, int32_t damage) +{ + Creature::drainHealth(attacker, damage); + sendStats(); +} + +void Player::drainMana(Creature* attacker, int32_t manaLoss) +{ + Creature::drainMana(attacker, manaLoss); + sendStats(); +} + +void Player::addManaSpent(uint64_t amount) +{ + if (hasFlag(PlayerFlag_NotGainMana)) { + return; + } + + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + if (currReqMana >= nextReqMana) { + //player has reached max magic level + return; + } + + amount *= g_config.getNumber(g_config.RATE_MAGIC); + + if (amount == 0) { + return; + } + + bool sendUpdateStats = false; + while ((manaSpent + amount) >= nextReqMana) { + amount -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + + g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); + + sendUpdateStats = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + if (currReqMana >= nextReqMana) { + return; + } + } + + manaSpent += amount; + + uint8_t oldPercent = magLevelPercent; + if (nextReqMana > currReqMana) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + if (oldPercent != magLevelPercent) { + sendUpdateStats = true; + } + + if (sendUpdateStats) { + sendStats(); + } +} + +void Player::addExperience(uint64_t exp, bool sendText/* = false*/, bool applyStages/* = true*/) +{ + uint64_t currLevelExp = Player::getExpForLevel(level); + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + if (currLevelExp >= nextLevelExp) { + //player has reached max level + levelPercent = 0; + sendStats(); + return; + } + + if (getSoul() < getVocation()->getSoulMax() && exp >= level) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SOUL, 4 * 60 * 1000, 0); + condition->setParam(CONDITION_PARAM_SOULGAIN, 1); + condition->setParam(CONDITION_PARAM_SOULTICKS, vocation->getSoulGainTicks() * 1000); + addCondition(condition); + } + + if (applyStages) { + exp *= g_game.getExperienceStage(level); + } + + if (exp == 0) { + return; + } + + experience += exp; + + if (sendText) { + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp)); + } + + uint32_t prevLevel = level; + while (experience >= nextLevelExp) { + ++level; + healthMax += vocation->getHPGain(); + health += vocation->getHPGain(); + manaMax += vocation->getManaGain(); + mana += vocation->getManaGain(); + capacity += vocation->getCapGain(); + + currLevelExp = nextLevelExp; + nextLevelExp = Player::getExpForLevel(level + 1); + if (currLevelExp >= nextLevelExp) { + //player has reached max level + break; + } + } + + if (prevLevel != level) { + updateBaseSpeed(); + setBaseSpeed(getBaseSpeed()); + + g_game.changeSpeed(this, 0); + g_game.addCreatureHealth(this); + + if (party) { + party->updateSharedExperience(); + } + + g_creatureEvents->playerAdvance(this, SKILL_LEVEL, prevLevel, level); + + std::ostringstream ss; + ss << "You advanced from Level " << prevLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + sendStats(); +} + +void Player::removeExperience(uint64_t exp) +{ + if (experience == 0 || exp == 0) { + return; + } + + experience = std::max(0, experience - exp); + + uint32_t oldLevel = level; + uint64_t currLevelExp = Player::getExpForLevel(level); + + while (level > 1 && experience < currLevelExp) { + --level; + healthMax = std::max(150, std::max(0, healthMax - vocation->getHPGain())); + manaMax = std::max(0, manaMax - vocation->getManaGain()); + capacity = std::max(400, std::max(0, capacity - vocation->getCapGain())); + currLevelExp = Player::getExpForLevel(level); + } + + if (oldLevel != level) { + health = healthMax; + mana = manaMax; + + updateBaseSpeed(); + setBaseSpeed(getBaseSpeed()); + + g_game.changeSpeed(this, 0); + g_game.addCreatureHealth(this); + + if (party) { + party->updateSharedExperience(); + } + + std::ostringstream ss; + ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + sendStats(); +} + +uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) +{ + if (nextLevelCount == 0) { + return 0; + } + + uint8_t result = (count * 100) / nextLevelCount; + if (result > 100) { + return 0; + } + return result; +} + +uint16_t Player::getDropLootPercent() +{ + return 10; +} + +void Player::onBlockHit() +{ + if (shieldBlockCount > 0) { + --shieldBlockCount; + + if (hasShield()) { + addSkillAdvance(SKILL_SHIELD, 1); + } + } +} + +void Player::onAttackedCreatureBlockHit(BlockType_t blockType) +{ + lastAttackBlockType = blockType; + + switch (blockType) { + case BLOCK_NONE: { + addAttackSkillPoint = true; + bloodHitCount = 30; + shieldBlockCount = 30; + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + //need to draw blood every 30 hits + if (bloodHitCount > 0) { + addAttackSkillPoint = true; + --bloodHitCount; + } else { + addAttackSkillPoint = false; + } + break; + } + + default: { + addAttackSkillPoint = false; + break; + } + } +} + +bool Player::hasShield() const +{ + Item* item = inventory[CONST_SLOT_LEFT]; + if (item && item->getWeaponType() == WEAPON_SHIELD) { + return true; + } + + item = inventory[CONST_SLOT_RIGHT]; + if (item && item->getWeaponType() == WEAPON_SHIELD) { + return true; + } + return false; +} + +BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) +{ + BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); + + if (attacker) { + sendCreatureSquare(attacker, SQ_COLOR_BLACK); + } + + if (blockType != BLOCK_NONE) { + return blockType; + } + + if (damage > 0) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + if (!isItemAbilityEnabled(static_cast(slot))) { + continue; + } + + Item* item = inventory[slot]; + if (!item) { + continue; + } + + const ItemType& it = Item::items[item->getID()]; + if (it.abilities) { + const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + if (absorbPercent != 0) { + damage -= std::round(damage * (absorbPercent / 100.)); + + uint16_t charges = item->getCharges() - 1; + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges); + } else { + g_game.internalRemoveItem(item); + } + } + + if (field) { + const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; + if (fieldAbsorbPercent != 0) { + damage -= std::round(damage * (fieldAbsorbPercent / 100.)); + + uint16_t charges = item->getCharges(); + if (charges != 0) { + if (charges - 1 == 0) { + g_game.internalRemoveItem(item); + } else { + g_game.transformItem(item, item->getID(), charges - 1); + } + } + } + } + } + } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + + return blockType; +} + +uint32_t Player::getIP() const +{ + if (client) { + return client->getIP(); + } + + return 0; +} + +void Player::dropLoot(Container* corpse, Creature*) +{ + if (corpse && lootDrop) { + Skulls_t playerSkull = getSkull(); + if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) { + g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1); + } else { + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (item) { + if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) { + g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); + sendInventoryItem(static_cast(i), nullptr); + } + } + } + } + } +} + +void Player::death(Creature* lastHitCreature) +{ + loginPosition = town->getTemplePosition(); + + if (skillLoss) { + //Magic level loss + uint64_t sumMana = 0; + uint64_t lostMana = 0; + + //sum up all the mana + for (uint32_t i = 1; i <= magLevel; ++i) { + sumMana += vocation->getReqMana(i); + } + + sumMana += manaSpent; + + double deathLossPercent = getLostPercent(); + + lostMana = static_cast(sumMana * deathLossPercent); + + while (lostMana > manaSpent && magLevel > 0) { + lostMana -= manaSpent; + manaSpent = vocation->getReqMana(magLevel); + magLevel--; + } + + manaSpent -= lostMana; + + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + if (nextReqMana > vocation->getReqMana(magLevel)) { + magLevelPercent = Player::getPercentLevel(manaSpent, nextReqMana); + } else { + magLevelPercent = 0; + } + + //Skill loss + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + uint64_t sumSkillTries = 0; + for (uint16_t c = 11; c <= skills[i].level; ++c) { //sum up all required tries for all skill levels + sumSkillTries += vocation->getReqSkillTries(i, c); + } + + sumSkillTries += skills[i].tries; + + uint32_t lostSkillTries = static_cast(sumSkillTries * deathLossPercent); + while (lostSkillTries > skills[i].tries) { + lostSkillTries -= skills[i].tries; + + if (skills[i].level <= 10) { + skills[i].level = 10; + skills[i].tries = 0; + lostSkillTries = 0; + break; + } + + skills[i].tries = vocation->getReqSkillTries(i, skills[i].level); + skills[i].level--; + } + + skills[i].tries = std::max(0, skills[i].tries - lostSkillTries); + skills[i].percent = Player::getPercentLevel(skills[i].tries, vocation->getReqSkillTries(i, skills[i].level)); + } + + //Level loss + uint64_t expLoss = static_cast(experience * deathLossPercent); + + if (expLoss != 0) { + uint32_t oldLevel = level; + + experience -= expLoss; + + while (level > 1 && experience < Player::getExpForLevel(level)) { + --level; + healthMax = std::max(0, healthMax - vocation->getHPGain()); + manaMax = std::max(0, manaMax - vocation->getManaGain()); + capacity = std::max(0, capacity - vocation->getCapGain()); + } + + if (oldLevel != level) { + std::ostringstream ss; + ss << "You were downgraded from Level " << oldLevel << " to Level " << level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint64_t currLevelExp = Player::getExpForLevel(level); + uint64_t nextLevelExp = Player::getExpForLevel(level + 1); + if (nextLevelExp > currLevelExp) { + levelPercent = Player::getPercentLevel(experience - currLevelExp, nextLevelExp - currLevelExp); + } else { + levelPercent = 0; + } + } + + std::bitset<6> bitset(blessings); + if (bitset[5]) { + if (Player::lastHitIsPlayer(lastHitCreature)) { + bitset.reset(5); + blessings = bitset.to_ulong(); + } else { + blessings = 32; + } + } else { + blessings = 0; + } + + sendStats(); + sendSkills(); + + health = healthMax; + mana = manaMax; + + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; + } else { + ++it; + } + } + + // Teleport newbies to newbie island + if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) { + if (getVocationId() != VOCATION_NONE && level <= static_cast(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) { + Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN)); + if (newbieTown) { + // Restart stats + level = 1; + experience = 0; + levelPercent = 0; + capacity = 400; + health = 150; + healthMax = 150; + mana = 0; + manaMax = 0; + magLevel = 0; + magLevelPercent = 0; + manaSpent = 0; + setVocation(0); + + // Restart skills + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + skills[i].level = 10; + skills[i].tries = 0; + skills[i].percent = 0; + } + + // Restart town + setTown(newbieTown); + loginPosition = getTemplePosition(); + + // Restart first items + lastLoginSaved = 0; + lastLogout = 0; + + // Restart items + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++) + { + Item* item = inventory[slot]; + if (item) { + g_game.internalRemoveItem(item, item->getItemCount()); + } + } + } else { + std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl; + } + } + } + } else { + setLossSkill(true); + + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; + if (condition->isPersistent()) { + it = conditions.erase(it); + + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; + } + else { + ++it; + } + } + + health = healthMax; + g_game.internalTeleport(this, getTemplePosition(), true); + g_game.addCreatureHealth(this); + onThink(EVENT_CREATURE_THINK_INTERVAL); + onIdleStatus(); + sendStats(); + } +} + +bool Player::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) +{ + if (getZone() != ZONE_PVP || !Player::lastHitIsPlayer(lastHitCreature)) { + return Creature::dropCorpse(lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + setDropLoot(true); + return false; +} + +Item* Player::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) +{ + Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); + if (corpse && corpse->getContainer()) { + std::ostringstream ss; + if (lastHitCreature) { + ss << "You recognize " << getNameDescription() << ". " << (getSex() == PLAYERSEX_FEMALE ? "She" : "He") << " was killed by " << lastHitCreature->getNameDescription() << '.'; + } else { + ss << "You recognize " << getNameDescription() << '.'; + } + + corpse->setSpecialDescription(ss.str()); + } + return corpse; +} + +void Player::addInFightTicks(bool pzlock /*= false*/) +{ + if (hasFlag(PlayerFlag_NotGainInFight)) { + return; + } + + if (pzlock) { + pzLocked = true; + } + + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::PZ_LOCKED), 0); + addCondition(condition); +} + +void Player::removeList() +{ + g_game.removePlayer(this); + + for (const auto& it : g_game.getPlayers()) { + it.second->notifyStatusChange(this, VIPSTATUS_OFFLINE); + } +} + +void Player::addList() +{ + for (const auto& it : g_game.getPlayers()) { + it.second->notifyStatusChange(this, VIPSTATUS_ONLINE); + } + + g_game.addPlayer(this); +} + +void Player::kickPlayer(bool displayEffect) +{ + g_creatureEvents->playerLogout(this); + if (client) { + client->logout(displayEffect, true); + } else { + g_game.removeCreature(this); + } +} + +void Player::notifyStatusChange(Player* loginPlayer, VipStatus_t status) +{ + if (!client) { + return; + } + + auto it = VIPList.find(loginPlayer->guid); + if (it == VIPList.end()) { + return; + } + + client->sendUpdatedVIPStatus(loginPlayer->guid, status); + + if (status == VIPSTATUS_ONLINE) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged in.")); + } else if (status == VIPSTATUS_OFFLINE) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, loginPlayer->getName() + " has logged out.")); + } +} + +bool Player::removeVIP(uint32_t vipGuid) +{ + if (VIPList.erase(vipGuid) == 0) { + return false; + } + + IOLoginData::removeVIPEntry(accountNumber, vipGuid); + return true; +} + +bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status) +{ + if (guid == vipGuid) { + sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself."); + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies."); + return false; + } + + auto result = VIPList.insert(vipGuid); + if (!result.second) { + sendTextMessage(MESSAGE_STATUS_SMALL, "This player is already in your list."); + return false; + } + + IOLoginData::addVIPEntry(accountNumber, vipGuid); + if (client) { + client->sendVIP(vipGuid, vipName, status); + } + return true; +} + +bool Player::addVIPInternal(uint32_t vipGuid) +{ + if (guid == vipGuid) { + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + return false; + } + + return VIPList.insert(vipGuid).second; +} + +//close container and its child containers +void Player::autoCloseContainers(const Container* container) +{ + std::vector closeList; + for (const auto& it : openContainers) { + Container* tmpContainer = it.second.container; + while (tmpContainer) { + if (tmpContainer->isRemoved() || tmpContainer == container) { + closeList.push_back(it.first); + break; + } + + tmpContainer = dynamic_cast(tmpContainer->getParent()); + } + } + + for (uint32_t containerId : closeList) { + closeContainer(containerId); + if (client) { + client->sendCloseContainer(containerId); + } + } +} + +bool Player::hasCapacity(const Item* item, uint32_t count) const +{ + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return false; + } + + if (hasFlag(PlayerFlag_HasInfiniteCapacity) || item->getTopParent() == this) { + return true; + } + + uint32_t itemWeight = item->getContainer() != nullptr ? item->getWeight() : item->getBaseWeight(); + if (item->isStackable()) { + itemWeight *= count; + } + return itemWeight <= getFreeCapacity(); +} + +ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature*) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags); + if (childIsOwner) { + //a child container is querying the player, just check if enough capacity + bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags); + if (skipLimit || hasCapacity(item, count)) { + return RETURNVALUE_NOERROR; + } + return RETURNVALUE_NOTENOUGHCAPACITY; + } + + if (!item->isPickupable()) { + return RETURNVALUE_CANNOTPICKUP; + } + + ReturnValue ret = RETURNVALUE_NOERROR; + + const int32_t& slotPosition = item->getSlotPosition(); + if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || + (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || + (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else if (slotPosition & SLOTP_TWO_HAND) { + ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; + } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { + ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; + } + + switch (index) { + case CONST_SLOT_HEAD: { + if (slotPosition & SLOTP_HEAD) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_NECKLACE: { + if (slotPosition & SLOTP_NECKLACE) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_BACKPACK: { + if (slotPosition & SLOTP_BACKPACK) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_ARMOR: { + if (slotPosition & SLOTP_ARMOR) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_RIGHT: { + if (slotPosition & SLOTP_RIGHT) { + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else if (inventory[CONST_SLOT_LEFT]) { + const Item* leftItem = inventory[CONST_SLOT_LEFT]; + WeaponType_t type = item->getWeaponType(), leftType = leftItem->getWeaponType(); + + if (leftItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RETURNVALUE_DROPTWOHANDEDITEM; + } else if (item == leftItem && count == item->getItemCount()) { + ret = RETURNVALUE_NOERROR; + } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RETURNVALUE_CANONLYUSEONESHIELD; + } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || + leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_NOERROR; + } else { + ret = RETURNVALUE_CANONLYUSEONEWEAPON; + } + } else { + ret = RETURNVALUE_NOERROR; + } + } + break; + } + + case CONST_SLOT_LEFT: { + if (slotPosition & SLOTP_LEFT) { + if (slotPosition & SLOTP_TWO_HAND) { + if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else if (inventory[CONST_SLOT_RIGHT]) { + const Item* rightItem = inventory[CONST_SLOT_RIGHT]; + WeaponType_t type = item->getWeaponType(), rightType = rightItem->getWeaponType(); + + if (rightItem->getSlotPosition() & SLOTP_TWO_HAND) { + ret = RETURNVALUE_DROPTWOHANDEDITEM; + } else if (item == rightItem && count == item->getItemCount()) { + ret = RETURNVALUE_NOERROR; + } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { + ret = RETURNVALUE_CANONLYUSEONESHIELD; + } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || + rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + ret = RETURNVALUE_NOERROR; + } else { + ret = RETURNVALUE_CANONLYUSEONEWEAPON; + } + } else { + ret = RETURNVALUE_NOERROR; + } + } + break; + } + + case CONST_SLOT_LEGS: { + if (slotPosition & SLOTP_LEGS) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_FEET: { + if (slotPosition & SLOTP_FEET) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_RING: { + if (slotPosition & SLOTP_RING) { + ret = RETURNVALUE_NOERROR; + } + break; + } + + case CONST_SLOT_AMMO: { + ret = RETURNVALUE_NOERROR; + break; + } + + case CONST_SLOT_WHEREEVER: + case -1: + ret = RETURNVALUE_NOTENOUGHROOM; + break; + + default: + ret = RETURNVALUE_NOTPOSSIBLE; + break; + } + + if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { + //need an exchange with source? + const Item* inventoryItem = getInventoryItem(static_cast(index)); + if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { + return RETURNVALUE_NEEDEXCHANGE; + } + + //check if enough capacity + if (!hasCapacity(item, count)) { + return RETURNVALUE_NOTENOUGHCAPACITY; + } + + if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { + return RETURNVALUE_CANNOTBEDRESSED; + } + } + return ret; +} + +ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const +{ + const Item* item = thing.getItem(); + if (item == nullptr) { + maxQueryCount = 0; + return RETURNVALUE_NOTPOSSIBLE; + } + + if (index == INDEX_WHEREEVER) { + uint32_t n = 0; + for (int32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + if (inventoryItem) { + if (Container* subContainer = inventoryItem->getContainer()) { + uint32_t queryCount = 0; + subContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); + n += queryCount; + + //iterate through all items, including sub-containers (deep search) + for (ContainerIterator it = subContainer->iterator(); it.hasNext(); it.advance()) { + if (Container* tmpContainer = (*it)->getContainer()) { + queryCount = 0; + tmpContainer->queryMaxCount(INDEX_WHEREEVER, *item, item->getItemCount(), queryCount, flags); + n += queryCount; + } + } + } else if (inventoryItem->isStackable() && item->equals(inventoryItem) && inventoryItem->getItemCount() < 100) { + uint32_t remainder = (100 - inventoryItem->getItemCount()); + + if (queryAdd(slotIndex, *item, remainder, flags) == RETURNVALUE_NOERROR) { + n += remainder; + } + } + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + if (item->isStackable()) { + n += 100; + } else { + ++n; + } + } + } + + maxQueryCount = n; + } else { + const Item* destItem = nullptr; + + const Thing* destThing = getThing(index); + if (destThing) { + destItem = destThing->getItem(); + } + + if (destItem) { + if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { + maxQueryCount = 100 - destItem->getItemCount(); + } else { + maxQueryCount = 0; + } + } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot + if (item->isStackable()) { + maxQueryCount = 100; + } else { + maxQueryCount = 1; + } + + return RETURNVALUE_NOERROR; + } + } + + if (maxQueryCount < count) { + return RETURNVALUE_NOTENOUGHROOM; + } else { + return RETURNVALUE_NOERROR; + } +} + +ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + + return RETURNVALUE_NOERROR; +} + +Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) +{ + if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { + *destItem = nullptr; + + const Item* item = thing.getItem(); + if (item == nullptr) { + return this; + } + + bool autoStack = !((flags & FLAG_IGNOREAUTOSTACK) == FLAG_IGNOREAUTOSTACK); + bool isStackable = item->isStackable(); + + std::vector containers; + + for (uint32_t slotIndex = CONST_SLOT_FIRST; slotIndex <= CONST_SLOT_LAST; ++slotIndex) { + Item* inventoryItem = inventory[slotIndex]; + if (inventoryItem) { + if (inventoryItem == tradeItem) { + continue; + } + + if (inventoryItem == item) { + continue; + } + + if (autoStack && isStackable) { + //try find an already existing item to stack with + if (queryAdd(slotIndex, *item, item->getItemCount(), 0) == RETURNVALUE_NOERROR) { + if (inventoryItem->equals(item) && inventoryItem->getItemCount() < 100) { + index = slotIndex; + *destItem = inventoryItem; + return this; + } + } + + if (Container* subContainer = inventoryItem->getContainer()) { + containers.push_back(subContainer); + } + } else if (Container* subContainer = inventoryItem->getContainer()) { + containers.push_back(subContainer); + } + } else if (queryAdd(slotIndex, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { //empty slot + index = slotIndex; + *destItem = nullptr; + return this; + } + } + + size_t i = 0; + while (i < containers.size()) { + Container* tmpContainer = containers[i++]; + if (!autoStack || !isStackable) { + //we need to find first empty container as fast as we can for non-stackable items + uint32_t n = tmpContainer->capacity() - tmpContainer->size(); + while (n) { + if (tmpContainer->queryAdd(tmpContainer->capacity() - n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + index = tmpContainer->capacity() - n; + *destItem = nullptr; + return tmpContainer; + } + + n--; + } + + /*for (Item* tmpContainerItem : tmpContainer->getItemList()) { + if (Container* subContainer = tmpContainerItem->getContainer()) { + containers.push_back(subContainer); + } + }*/ + + continue; + } + + uint32_t n = 0; + + for (Item* tmpItem : tmpContainer->getItemList()) { + if (tmpItem == tradeItem) { + continue; + } + + if (tmpItem == item) { + continue; + } + + //try find an already existing item to stack with + if (tmpItem->equals(item) && tmpItem->getItemCount() < 100) { + index = n; + *destItem = tmpItem; + return tmpContainer; + } + + /*if (Container* subContainer = tmpItem->getContainer()) { + containers.push_back(subContainer); + }*/ + + n++; + } + + if (n < tmpContainer->capacity() && tmpContainer->queryAdd(n, *item, item->getItemCount(), flags) == RETURNVALUE_NOERROR) { + index = n; + *destItem = nullptr; + return tmpContainer; + } + } + + return this; + } + + Thing* destThing = getThing(index); + if (destThing) { + *destItem = destThing->getItem(); + } + + Cylinder* subCylinder = dynamic_cast(destThing); + if (subCylinder) { + index = INDEX_WHEREEVER; + *destItem = nullptr; + return subCylinder; + } else { + return this; + } +} + +void Player::addThing(int32_t index, Thing* thing) +{ + if (index < CONST_SLOT_FIRST || index > CONST_SLOT_LAST) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + inventory[index] = item; + + //send to client + sendInventoryItem(static_cast(index), item); +} + +void Player::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setID(itemId); + item->setSubType(count); + + //send to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(item, item); +} + +void Player::replaceThing(uint32_t index, Thing* thing) +{ + if (index > CONST_SLOT_LAST) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* oldItem = getInventoryItem(static_cast(index)); + if (!oldItem) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + //send to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(oldItem, item); + + item->setParent(this); + + inventory[index] = item; +} + +void Player::removeThing(Thing* thing, uint32_t count) +{ + Item* item = thing->getItem(); + if (!item) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (item->isStackable()) { + if (count == item->getItemCount()) { + //send change to client + sendInventoryItem(static_cast(index), nullptr); + + //event methods + onRemoveInventoryItem(item); + + item->setParent(nullptr); + inventory[index] = nullptr; + } else { + uint8_t newCount = static_cast(std::max(0, item->getItemCount() - count)); + item->setItemCount(newCount); + + //send change to client + sendInventoryItem(static_cast(index), item); + + //event methods + onUpdateInventoryItem(item, item); + } + } else { + //send change to client + sendInventoryItem(static_cast(index), nullptr); + + //event methods + onRemoveInventoryItem(item); + + item->setParent(nullptr); + inventory[index] = nullptr; + } +} + +int32_t Player::getThingIndex(const Thing* thing) const +{ + for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + if (inventory[i] == thing) { + return i; + } + } + return -1; +} + +size_t Player::getFirstIndex() const +{ + return CONST_SLOT_FIRST; +} + +size_t Player::getLastIndex() const +{ + return CONST_SLOT_LAST + 1; +} + +uint32_t Player::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) 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::countByType(item, subType); + } + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + count += Item::countByType(*it, subType); + } + } + } + } + return count; +} + +bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped/* = false*/) const +{ + if (amount == 0) { + return true; + } + + std::vector itemList; + + uint32_t count = 0; + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + if (!ignoreEquipped && item->getID() == itemId) { + uint32_t itemCount = Item::countByType(item, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(item); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + else if (Container* container = item->getContainer()) { + if (container->getID() == itemId) { + uint32_t itemCount = Item::countByType(item, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(item); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + Item* containerItem = *it; + if (containerItem->getID() == itemId) { + uint32_t itemCount = Item::countByType(containerItem, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(containerItem); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + } + } + } + return false; +} + +std::map& Player::getAllItemTypeCount(std::map& countMap) const +{ + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; i++) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + countMap[item->getID()] += Item::countByType(item, -1); + + if (Container* container = item->getContainer()) { + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + countMap[(*it)->getID()] += Item::countByType(*it, -1); + } + } + } + return countMap; +} + +Thing* Player::getThing(size_t index) const +{ + if (index >= CONST_SLOT_FIRST && index <= CONST_SLOT_LAST) { + return inventory[index]; + } + return nullptr; +} + +void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); + } + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + onSendContainer(container); + } + } else if (const Creature* creature = thing->getCreature()) { + if (creature == this) { + //check containers + std::vector containers; + + for (const auto& it : openContainers) { + Container* container = it.second.container; + if (!Position::areInRange<1, 1, 0>(container->getPosition(), getPosition())) { + containers.push_back(container); + } + } + + for (const Container* container : containers) { + autoCloseContainers(container); + } + } + } +} + +void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + if (link == LINK_OWNER) { + //calling movement scripts + g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); + } + + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + updateInventoryWeight(); + updateItemsLight(); + sendStats(); + } + + if (const Item* item = thing->getItem()) { + if (const Container* container = item->getContainer()) { + if (container->isRemoved() || !Position::areInRange<1, 1, 0>(getPosition(), container->getPosition())) { + autoCloseContainers(container); + } else if (container->getTopParent() == this) { + onSendContainer(container); + } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { + if (const DepotLocker* depotLocker = dynamic_cast(topContainer)) { + bool isOwner = false; + + for (const auto& it : depotLockerMap) { + if (it.second == depotLocker) { + isOwner = true; + onSendContainer(container); + } + } + + if (!isOwner) { + autoCloseContainers(container); + } + } else { + onSendContainer(container); + } + } else { + autoCloseContainers(container); + } + } + } +} + +void Player::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Player::internalAddThing(uint32_t index, Thing* thing) +{ + Item* item = thing->getItem(); + if (!item) { + return; + } + + //index == 0 means we should equip this item at the most appropiate slot (no action required here) + if (index > 0 && index < 11) { + if (inventory[index]) { + return; + } + + inventory[index] = item; + item->setParent(this); + } +} + +uint32_t Player::checkPlayerKilling() +{ + time_t today = std::time(nullptr); + uint32_t lastDay = 0; + uint32_t lastWeek = 0; + uint32_t lastMonth = 0; + uint64_t egibleMurders = 0; + + time_t dayTimestamp = today - (24 * 60 * 60); + time_t weekTimestamp = today - (7 * 24 * 60 * 60); + time_t monthTimestamp = today - (30 * 24 * 60 * 60); + + for (time_t currentMurderTimestamp : murderTimeStamps) { + if (currentMurderTimestamp > dayTimestamp) { + lastDay++; + } + + if (currentMurderTimestamp > weekTimestamp) { + lastWeek++; + } + + egibleMurders = lastMonth + 1; + + if (currentMurderTimestamp <= monthTimestamp) { + egibleMurders = lastMonth; + } + + lastMonth = egibleMurders; + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) { + return 2; // banishment! + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) { + return 1; // red skull! + } + + return 0; +} + +bool Player::setFollowCreature(Creature* creature) +{ + if (!Creature::setFollowCreature(creature)) { + setFollowCreature(nullptr); + setAttackedCreature(nullptr); + + sendCancelMessage(RETURNVALUE_THEREISNOWAY); + sendCancelTarget(); + stopWalk(); + return false; + } + return true; +} + +bool Player::setAttackedCreature(Creature* creature) +{ + if (!Creature::setAttackedCreature(creature)) { + sendCancelTarget(); + return false; + } + + if (chaseMode == CHASEMODE_FOLLOW && creature) { + if (followCreature != creature) { + //chase opponent + setFollowCreature(creature); + } + } else if (followCreature) { + setFollowCreature(nullptr); + } + + if (creature) { + g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); + } + return true; +} + +void Player::goToFollowCreature() +{ + if (!walkTask) { + if ((OTSYS_TIME() - lastFailedFollow) < 2000) { + return; + } + + Creature::goToFollowCreature(); + + if (followCreature && !hasFollowPath) { + lastFailedFollow = OTSYS_TIME(); + } + } +} + +void Player::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const +{ + Creature::getPathSearchParams(creature, fpp); + fpp.fullPathSearch = true; +} + +void Player::doAttacking(uint32_t) +{ + if (lastAttack == 0) { + lastAttack = OTSYS_TIME() - getAttackSpeed() - 1; + } + + if (hasCondition(CONDITION_PACIFIED)) { + return; + } + + if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { + if (Combat::attack(this, attackedCreature)) { + earliestAttackTime = OTSYS_TIME() + 2000; + lastAttack = OTSYS_TIME(); + } + } +} + +uint64_t Player::getGainedExperience(Creature* attacker) const +{ + if (g_config.getBoolean(ConfigManager::EXPERIENCE_FROM_PLAYERS)) { + Player* attackerPlayer = attacker->getPlayer(); + if (attackerPlayer && attackerPlayer != this && skillLoss && std::abs(static_cast(attackerPlayer->getLevel() - level)) <= g_config.getNumber(ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE)) { + return std::max(0, std::floor(getLostExperience() * getDamageRatio(attacker) * 0.75)); + } + } + return 0; +} + +void Player::onFollowCreature(const Creature* creature) +{ + if (!creature) { + stopWalk(); + } +} + +void Player::setChaseMode(chaseMode_t mode) +{ + chaseMode_t prevChaseMode = chaseMode; + chaseMode = mode; + + if (prevChaseMode != chaseMode) { + if (chaseMode == CHASEMODE_FOLLOW) { + if (!followCreature && attackedCreature) { + //chase opponent + setFollowCreature(attackedCreature); + } + } else if (attackedCreature) { + setFollowCreature(nullptr); + cancelNextWalk = true; + } + } +} + +void Player::onWalkAborted() +{ + setNextWalkActionTask(nullptr); + sendCancelWalk(); +} + +void Player::onWalkComplete() +{ + if (walkTask) { + walkTaskEvent = g_scheduler.addEvent(walkTask); + walkTask = nullptr; + } +} + +void Player::stopWalk() +{ + cancelNextWalk = true; +} + +void Player::getCreatureLight(LightInfo& light) const +{ + if (internalLight.level > itemsLight.level) { + light = internalLight; + } else { + light = itemsLight; + } +} + +void Player::updateItemsLight(bool internal /*=false*/) +{ + LightInfo maxLight; + LightInfo curLight; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (item) { + item->getLight(curLight); + + if (curLight.level > maxLight.level) { + maxLight = curLight; + } + } + } + + if (itemsLight.level != maxLight.level || itemsLight.color != maxLight.color) { + itemsLight = maxLight; + + if (!internal) { + g_game.changeLight(this); + } + } +} + +void Player::onAddCondition(ConditionType_t type) +{ + Creature::onAddCondition(type); + sendIcons(); +} + +void Player::onAddCombatCondition(ConditionType_t type) +{ + switch (type) { + case CONDITION_POISON: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); + break; + + case CONDITION_PARALYZE: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); + break; + + case CONDITION_DRUNK: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); + break; + + default: + break; + } +} + +void Player::onEndCondition(ConditionType_t type) +{ + Creature::onEndCondition(type); + + if (type == CONDITION_INFIGHT) { + onIdleStatus(); + pzLocked = false; + clearAttacked(); + + if (getSkull() != SKULL_RED) { + setSkull(SKULL_NONE); + } + } + + sendIcons(); +} + +void Player::onCombatRemoveCondition(Condition* condition) +{ + //Creature::onCombatRemoveCondition(condition); + if (condition->getId() > 0) { + //Means the condition is from an item, id == slot + if (g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + Item* item = getInventoryItem(static_cast(condition->getId())); + if (item) { + //25% chance to destroy the item + if (25 >= uniform_random(1, 100)) { + g_game.internalRemoveItem(item); + } + } + } + } else { + if (!canDoAction()) { + const uint32_t delay = getNextActionTime(); + const int32_t ticks = delay - (delay % EVENT_CREATURE_THINK_INTERVAL); + if (ticks < 0) { + removeCondition(condition); + } else { + condition->setTicks(ticks); + } + } else { + removeCondition(condition); + } + } +} + +void Player::onAttackedCreature(Creature* target) +{ + Creature::onAttackedCreature(target); + + if (target->getZone() == ZONE_PVP) { + return; + } + + if (target == this) { + addInFightTicks(); + return; + } + + if (hasFlag(PlayerFlag_NotGainInFight)) { + return; + } + + Player* targetPlayer = target->getPlayer(); + if (targetPlayer) { + if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + pzLocked = true; + sendIcons(); + } + + if (!isPartner(targetPlayer)) { + if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + addAttacked(targetPlayer); + targetPlayer->sendCreatureSkull(this); + } else { + if (!targetPlayer->hasAttacked(this)) { + if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { + addAttacked(targetPlayer); + if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { + setSkull(SKULL_WHITE); + } + } + + if (getSkull() == SKULL_NONE) { + targetPlayer->sendCreatureSkull(this); + } + } + } + } + } + + addInFightTicks(); +} + +void Player::onAttacked() +{ + Creature::onAttacked(); + + addInFightTicks(); +} + +void Player::onIdleStatus() +{ + Creature::onIdleStatus(); + + if (party) { + party->clearPlayerPoints(this); + } +} + +void Player::onPlacedCreature() +{ + //scripting event - onLogin + if (!g_creatureEvents->playerLogin(this)) { + kickPlayer(true); + } +} + +void Player::onAttackedCreatureDrainHealth(Creature* target, int32_t points) +{ + Creature::onAttackedCreatureDrainHealth(target, points); + + if (target) { + if (party && !Combat::isPlayerCombat(target)) { + Monster* tmpMonster = target->getMonster(); + if (tmpMonster && tmpMonster->isHostile()) { + //We have fulfilled a requirement for shared experience + party->updatePlayerTicks(this, points); + } + } + } +} + +void Player::onTargetCreatureGainHealth(Creature* target, int32_t points) +{ + if (target && party) { + Player* tmpPlayer = nullptr; + + if (target->getPlayer()) { + tmpPlayer = target->getPlayer(); + } else if (Creature* targetMaster = target->getMaster()) { + if (Player* targetMasterPlayer = targetMaster->getPlayer()) { + tmpPlayer = targetMasterPlayer; + } + } + + if (isPartner(tmpPlayer)) { + party->updatePlayerTicks(this, points); + } + } +} + +bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) +{ + bool unjustified = false; + + if (hasFlag(PlayerFlag_NotGenerateLoot)) { + target->setDropLoot(false); + } + + Creature::onKilledCreature(target, lastHit); + + if (Player* targetPlayer = target->getPlayer()) { + if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { + targetPlayer->setDropLoot(false); + targetPlayer->setLossSkill(false); + } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { + if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { + if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { + unjustified = true; + addUnjustifiedDead(targetPlayer); + } + } + } + + if (lastHit && hasCondition(CONDITION_INFIGHT)) { + pzLocked = true; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); + addCondition(condition); + } + } + + return unjustified; +} + +void Player::gainExperience(uint64_t gainExp) +{ + if (hasFlag(PlayerFlag_NotGainExperience) || gainExp == 0) { + return; + } + + addExperience(gainExp, true); +} + +void Player::onGainExperience(uint64_t gainExp, Creature* target) +{ + if (hasFlag(PlayerFlag_NotGainExperience)) { + return; + } + + if (target && !target->getPlayer() && party && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { + party->shareExperience(gainExp); + //We will get a share of the experience through the sharing mechanism + return; + } + + Creature::onGainExperience(gainExp, target); + gainExperience(gainExp); +} + +void Player::onGainSharedExperience(uint64_t gainExp) +{ + gainExperience(gainExp); +} + +bool Player::isImmune(CombatType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + return Creature::isImmune(type); +} + +bool Player::isImmune(ConditionType_t type) const +{ + if (hasFlag(PlayerFlag_CannotBeAttacked)) { + return true; + } + return Creature::isImmune(type); +} + +bool Player::isAttackable() const +{ + return !hasFlag(PlayerFlag_CannotBeAttacked); +} + +bool Player::lastHitIsPlayer(Creature* lastHitCreature) +{ + if (!lastHitCreature) { + return false; + } + + if (lastHitCreature->getPlayer()) { + return true; + } + + Creature* lastHitMaster = lastHitCreature->getMaster(); + return lastHitMaster && lastHitMaster->getPlayer(); +} + +void Player::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) +{ + Creature::changeHealth(healthChange, sendHealthChange); + sendStats(); +} + +void Player::changeMana(int32_t manaChange) +{ + if (!hasFlag(PlayerFlag_HasInfiniteMana)) { + Creature::changeMana(manaChange); + } + + sendStats(); +} + +void Player::changeSoul(int32_t soulChange) +{ + if (soulChange > 0) { + soul += std::min(soulChange, vocation->getSoulMax() - soul); + } else { + soul = std::max(0, soul + soulChange); + } + + sendStats(); +} + +bool Player::canWear(uint32_t lookType) const +{ + if (group->access) { + return true; + } + + if (getSex() == PLAYERSEX_MALE) { + if (lookType >= 132 && lookType <= 134 && isPremium()) { + return true; + } else if (lookType >= 128 && lookType <= 131) { + return true; + } + } else if (getSex() == PLAYERSEX_FEMALE) { + if (lookType >= 140 && lookType <= 142 && isPremium()) { + return true; + } else if (lookType >= 136 && lookType <= 139) { + return true; + } + } + + return false; +} + +bool Player::canLogout() +{ + if (isConnecting) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + return false; + } + + if (getTile()->hasFlag(TILESTATE_PROTECTIONZONE)) { + return true; + } + + return !isPzLocked() && !hasCondition(CONDITION_INFIGHT); +} + +void Player::setSex(PlayerSex_t newSex) +{ + sex = newSex; +} + +Skulls_t Player::getSkull() const +{ + if (hasFlag(PlayerFlag_NotGainInFight)) { + return SKULL_NONE; + } + return skull; +} + +Skulls_t Player::getSkullClient(const Creature* creature) const +{ + if (!creature || g_game.getWorldType() != WORLD_TYPE_PVP) { + return SKULL_NONE; + } + + const Player* player = creature->getPlayer(); + if (player && player->getSkull() == SKULL_NONE) { + if (isInWar(player)) { + return SKULL_GREEN; + } + + if (!player->getGuildWarList().empty() && guild == player->getGuild()) { + return SKULL_GREEN; + } + + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } + + if (isPartner(player)) { + return SKULL_GREEN; + } + } + return Creature::getSkullClient(creature); +} + +bool Player::hasAttacked(const Player* attacked) const +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) { + return false; + } + + return attackedSet.find(attacked->id) != attackedSet.end(); +} + +void Player::addAttacked(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || !attacked || attacked == this) { + return; + } + + attackedSet.insert(attacked->id); +} + +void Player::clearAttacked() +{ + attackedSet.clear(); +} + +void Player::addUnjustifiedDead(const Player* attacked) +{ + if (hasFlag(PlayerFlag_NotGainInFight) || attacked == this || g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + return; + } + + // current unjustified kill! + murderTimeStamps.push_back(std::time(nullptr)); + + sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified."); + + if (playerKillerEnd == 0) { + // white skull time, it only sets on first kill! + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME); + } + + uint32_t murderResult = checkPlayerKilling(); + if (murderResult >= 1) { + // red skull player + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME); + setSkull(SKULL_RED); + + if (murderResult == 2) { + // banishment for too many unjustified kills + Database* db = Database::getInstance(); + + std::ostringstream ss; + ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ("; + ss << getAccount() << ", "; + ss << db->escapeString("Too many unjustified kills") << ", "; + ss << std::time(nullptr) << ", "; + ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", "; + ss << "1);"; + + db->executeQuery(ss.str()); + + g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS); + g_game.removeCreature(this); + disconnect(); + } + } +} + +void Player::checkSkullTicks() +{ + time_t today = std::time(nullptr); + + if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) { + setSkull(SKULL_NONE); + } +} + +bool Player::isPromoted() const +{ + uint16_t promotedVocation = g_vocations.getPromotedVocation(vocation->getId()); + return promotedVocation == VOCATION_NONE && vocation->getId() != promotedVocation; +} + +double Player::getLostPercent() const +{ + int32_t blessingCount = std::bitset<5>(blessings).count(); + + int32_t deathLosePercent = g_config.getNumber(ConfigManager::DEATH_LOSE_PERCENT); + if (deathLosePercent != -1) { + if (isPromoted()) { + deathLosePercent -= 3; + } + + deathLosePercent -= blessingCount; + return std::max(0, deathLosePercent) / 100.; + } + + double lossPercent; + if (level >= 25) { + double tmpLevel = level + (levelPercent / 100.); + lossPercent = static_cast((tmpLevel + 50) * 50 * ((tmpLevel * tmpLevel) - (5 * tmpLevel) + 8)) / experience; + } else { + lossPercent = 10; + } + + if (isPromoted()) { + lossPercent *= 0.7; + } + + return lossPercent * pow(0.92, blessingCount) / 100; +} + +void Player::learnInstantSpell(const std::string& spellName) +{ + if (!hasLearnedInstantSpell(spellName)) { + learnedInstantSpellList.push_front(spellName); + } +} + +void Player::forgetInstantSpell(const std::string& spellName) +{ + learnedInstantSpellList.remove(spellName); +} + +bool Player::hasLearnedInstantSpell(const std::string& spellName) const +{ + if (hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + for (const auto& learnedSpellName : learnedInstantSpellList) { + if (strcasecmp(learnedSpellName.c_str(), spellName.c_str()) == 0) { + return true; + } + } + return false; +} + +bool Player::isInWar(const Player* player) const +{ + if (!player || !guild) { + return false; + } + + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + return false; + } + + return isInWarList(playerGuild->getId()) && player->isInWarList(guild->getId()); +} + +bool Player::isInWarList(uint32_t guildId) const +{ + return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end(); +} + +bool Player::isPremium() const +{ + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM) || hasFlag(PlayerFlag_IsAlwaysPremium)) { + return true; + } + + return premiumDays > 0; +} + +void Player::setPremiumDays(int32_t v) +{ + premiumDays = v; +} + +PartyShields_t Player::getPartyShield(const Player* player) const +{ + if (!player) { + return SHIELD_NONE; + } + + if (party) { + if (party->getLeader() == player) { + return SHIELD_YELLOW; + } + + if (player->party == party) { + return SHIELD_BLUE; + } + + if (isInviting(player)) { + return SHIELD_WHITEBLUE; + } + } + + if (player->isInviting(this)) { + return SHIELD_WHITEYELLOW; + } + + return SHIELD_NONE; +} + +bool Player::isInviting(const Player* player) const +{ + if (!player || !party || party->getLeader() != this) { + return false; + } + return party->isPlayerInvited(player); +} + +bool Player::isPartner(const Player* player) const +{ + if (!player || !party) { + return false; + } + return party == player->party; +} + +bool Player::isGuildMate(const Player* player) const +{ + if (!player || !guild) { + return false; + } + return guild == player->guild; +} + +void Player::sendPlayerPartyIcons(Player* player) +{ + sendCreatureShield(player); + sendCreatureSkull(player); +} + +bool Player::addPartyInvitation(Party* party) +{ + auto it = std::find(invitePartyList.begin(), invitePartyList.end(), party); + if (it != invitePartyList.end()) { + return false; + } + + invitePartyList.push_front(party); + return true; +} + +void Player::removePartyInvitation(Party* party) +{ + invitePartyList.remove(party); +} + +void Player::clearPartyInvitations() +{ + for (Party* invitingParty : invitePartyList) { + invitingParty->removeInvite(*this, false); + } + invitePartyList.clear(); +} + +void Player::sendClosePrivate(uint16_t channelId) +{ + if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { + g_chat->removeUserFromChannel(*this, channelId); + } + + if (client) { + client->sendClosePrivate(channelId); + } +} + +uint64_t Player::getMoney() const +{ + std::vector containers; + uint64_t moneyCount = 0; + + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (!item) { + continue; + } + + const Container* container = item->getContainer(); + if (container) { + containers.push_back(container); + } else { + moneyCount += item->getWorth(); + } + } + + size_t i = 0; + while (i < containers.size()) { + const Container* container = containers[i++]; + for (const Item* item : container->getItemList()) { + const Container* tmpContainer = item->getContainer(); + if (tmpContainer) { + containers.push_back(tmpContainer); + } else { + moneyCount += item->getWorth(); + } + } + } + return moneyCount; +} + +size_t Player::getMaxVIPEntries() const +{ + if (group->maxVipEntries != 0) { + return group->maxVipEntries; + } else if (isPremium()) { + return 100; + } + return 20; +} + +size_t Player::getMaxDepotItems() const +{ + if (group->maxDepotItems != 0) { + return group->maxDepotItems; + } else if (isPremium()) { + return 2000; + } + + return 1000; +} + +std::forward_list Player::getMuteConditions() const +{ + std::forward_list muteConditions; + for (Condition* condition : conditions) { + if (condition->getTicks() <= 0) { + continue; + } + + ConditionType_t type = condition->getType(); + if (type != CONDITION_MUTED && type != CONDITION_CHANNELMUTEDTICKS && type != CONDITION_YELLTICKS) { + continue; + } + + muteConditions.push_front(condition); + } + return muteConditions; +} + +void Player::setGuild(Guild* guild) +{ + if (guild == this->guild) { + return; + } + + Guild* oldGuild = this->guild; + + this->guildNick.clear(); + this->guild = nullptr; + this->guildRank = nullptr; + + if (guild) { + const GuildRank* rank = guild->getRankByLevel(1); + if (!rank) { + return; + } + + this->guild = guild; + this->guildRank = rank; + guild->addMember(this); + } + + if (oldGuild) { + oldGuild->removeMember(this); + } +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..40c05e1 --- /dev/null +++ b/src/player.h @@ -0,0 +1,1099 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F +#define FS_PLAYER_H_4083D3D3A05B4EDE891B31BB720CD06F + +#include "creature.h" +#include "container.h" +#include "cylinder.h" +#include "enums.h" +#include "vocation.h" +#include "protocolgame.h" +#include "ioguild.h" +#include "party.h" +#include "depotlocker.h" +#include "guild.h" +#include "groups.h" +#include "town.h" + +class BehaviourDatabase; +class House; +class NetworkMessage; +class Weapon; +class ProtocolGame; +class Npc; +class Party; +class SchedulerTask; +class Bed; +class Guild; + +enum skillsid_t { + SKILLVALUE_LEVEL = 0, + SKILLVALUE_TRIES = 1, + SKILLVALUE_PERCENT = 2, +}; + +enum chaseMode_t : uint8_t { + CHASEMODE_STANDSTILL = 0, + CHASEMODE_FOLLOW = 1, +}; + +enum tradestate_t : uint8_t { + TRADE_NONE, + TRADE_INITIATED, + TRADE_ACCEPT, + TRADE_ACKNOWLEDGE, + TRADE_TRANSFER, +}; + +struct VIPEntry { + VIPEntry(uint32_t guid, std::string name) : + guid(guid), name(std::move(name)) {} + + uint32_t guid; + std::string name; +}; + +struct OpenContainer { + Container* container; + uint16_t index; +}; + +struct OutfitEntry { + constexpr OutfitEntry(uint16_t lookType) : lookType(lookType) {} + + uint16_t lookType; +}; + +struct Skill { + uint64_t tries = 0; + uint16_t level = 10; + uint8_t percent = 0; +}; + +typedef std::map MuteCountMap; + +static constexpr int32_t PLAYER_MAX_SPEED = 1500; +static constexpr int32_t PLAYER_MIN_SPEED = 0; + +class Player final : public Creature, public Cylinder +{ + public: + explicit Player(ProtocolGame_ptr p); + ~Player(); + + // non-copyable + Player(const Player&) = delete; + Player& operator=(const Player&) = delete; + + Player* getPlayer() final { + return this; + } + const Player* getPlayer() const final { + return this; + } + + void setID() final { + if (id == 0) { + id = playerAutoID++; + } + } + + static MuteCountMap muteCountMap; + + const std::string& getName() const final { + return name; + } + void setName(std::string name) { + this->name = std::move(name); + } + const std::string& getNameDescription() const final { + return name; + } + std::string getDescription(int32_t lookDistance) const final; + + void setGUID(uint32_t guid) { + this->guid = guid; + } + uint32_t getGUID() const { + return guid; + } + bool canSeeInvisibility() const final { + return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; + } + + void removeList() final; + void addList() final; + void kickPlayer(bool displayEffect); + + static uint64_t getExpForLevel(int32_t lv) { + lv--; + return ((50ULL * lv * lv * lv) - (150ULL * lv * lv) + (400ULL * lv)) / 3ULL; + } + + uint64_t getBankBalance() const { + return bankBalance; + } + void setBankBalance(uint64_t balance) { + bankBalance = balance; + } + + Guild* getGuild() const { + return guild; + } + void setGuild(Guild* guild); + + const GuildRank* getGuildRank() const { + return guildRank; + } + void setGuildRank(const GuildRank* newGuildRank) { + guildRank = newGuildRank; + } + + bool isGuildMate(const Player* player) const; + + const std::string& getGuildNick() const { + return guildNick; + } + void setGuildNick(std::string nick) { + guildNick = nick; + } + + bool isInWar(const Player* player) const; + bool isInWarList(uint32_t guild_id) const; + + uint16_t getClientIcons() const; + + const GuildWarList& getGuildWarList() const { + return guildWarList; + } + + Vocation* getVocation() const { + return vocation; + } + + OperatingSystem_t getOperatingSystem() const { + return operatingSystem; + } + void setOperatingSystem(OperatingSystem_t clientos) { + operatingSystem = clientos; + } + + uint16_t getProtocolVersion() const { + if (!client) { + return 0; + } + + return client->getVersion(); + } + + bool hasSecureMode() const { + return secureMode; + } + + void setParty(Party* party) { + this->party = party; + } + Party* getParty() const { + return party; + } + PartyShields_t getPartyShield(const Player* player) const; + bool isInviting(const Player* player) const; + bool isPartner(const Player* player) const; + void sendPlayerPartyIcons(Player* player); + bool addPartyInvitation(Party* party); + void removePartyInvitation(Party* party); + void clearPartyInvitations(); + + uint64_t getSpentMana() const { + return manaSpent; + } + + bool hasFlag(PlayerFlags value) const { + return (group->flags & value) != 0; + } + + BedItem* getBedItem() { + return bedItem; + } + void setBedItem(BedItem* b) { + bedItem = b; + } + + void addBlessing(uint8_t blessing) { + blessings |= blessing; + } + void removeBlessing(uint8_t blessing) { + blessings &= ~blessing; + } + bool hasBlessing(uint8_t value) const { + return (blessings & (static_cast(1) << value)) != 0; + } + + bool isOffline() const { + return (getID() == 0); + } + void disconnect() { + if (client) { + client->disconnect(); + } + } + uint32_t getIP() const; + + void addContainer(uint8_t cid, Container* container); + void closeContainer(uint8_t cid); + void setContainerIndex(uint8_t cid, uint16_t index); + + Container* getContainerByID(uint8_t cid); + int8_t getContainerID(const Container* container) const; + uint16_t getContainerIndex(uint8_t cid) const; + + void addStorageValue(const uint32_t key, const int32_t value); + bool getStorageValue(const uint32_t key, int32_t& value) const; + + void setGroup(Group* newGroup) { + group = newGroup; + } + Group* getGroup() const { + return group; + } + + void resetIdleTime() { + idleTime = 0; + } + + bool isInGhostMode() const { + return ghostMode; + } + void switchGhostMode() { + ghostMode = !ghostMode; + } + + uint32_t getAccount() const { + return accountNumber; + } + AccountType_t getAccountType() const { + return accountType; + } + uint32_t getLevel() const { + return level; + } + uint8_t getLevelPercent() const { + return levelPercent; + } + uint32_t getMagicLevel() const { + return std::max(0, magLevel + varStats[STAT_MAGICPOINTS]); + } + uint32_t getBaseMagicLevel() const { + return magLevel; + } + uint8_t getMagicLevelPercent() const { + return magLevelPercent; + } + uint8_t getSoul() const { + return soul; + } + bool isAccessPlayer() const { + return group->access; + } + bool isPremium() const; + void setPremiumDays(int32_t v); + + bool setVocation(uint16_t vocId); + uint16_t getVocationId() const { + return vocation->getId(); + } + uint16_t getVocationFlagId() const { + return vocation->getFlagId(); + } + + PlayerSex_t getSex() const { + return sex; + } + void setSex(PlayerSex_t); + uint64_t getExperience() const { + return experience; + } + + time_t getLastLoginSaved() const { + return lastLoginSaved; + } + + time_t getLastLogout() const { + return lastLogout; + } + + const Position& getLoginPosition() const { + return loginPosition; + } + const Position& getTemplePosition() const { + return town->getTemplePosition(); + } + Town* getTown() const { + return town; + } + void setTown(Town* town) { + this->town = town; + } + + bool isPushable() const final; + uint32_t isMuted() const; + void addMessageBuffer(); + void removeMessageBuffer(); + + bool removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, bool ignoreEquipped = false) const; + + uint32_t getCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); + } + return capacity; + } + + uint32_t getFreeCapacity() const { + if (hasFlag(PlayerFlag_CannotPickupItem)) { + return 0; + } else if (hasFlag(PlayerFlag_HasInfiniteCapacity)) { + return std::numeric_limits::max(); + } else { + return std::max(0, capacity - inventoryWeight); + } + } + + int32_t getMaxHealth() const final { + return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); + } + uint32_t getMaxMana() const { + return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); + } + + Item* getInventoryItem(slots_t slot) const; + + bool isItemAbilityEnabled(slots_t slot) const { + return inventoryAbilities[slot]; + } + void setItemAbility(slots_t slot, bool enabled) { + inventoryAbilities[slot] = enabled; + } + + void setVarSkill(skills_t skill, int32_t modifier) { + varSkills[skill] += modifier; + } + + void setVarStats(stats_t stat, int32_t modifier); + int32_t getDefaultStats(stats_t stat) const; + + void addConditionSuppressions(uint32_t conditions); + void removeConditionSuppressions(uint32_t conditions); + + DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate); + void onReceiveMail() const; + bool isNearDepotBox() const; + + bool canSee(const Position& pos) const final; + bool canSeeCreature(const Creature* creature) const final; + + RaceType_t getRace() const final { + return RACE_BLOOD; + } + + uint64_t getMoney() const; + + //safe-trade functions + void setTradeState(tradestate_t state) { + tradeState = state; + } + tradestate_t getTradeState() const { + return tradeState; + } + Item* getTradeItem() { + return tradeItem; + } + + //V.I.P. functions + void notifyStatusChange(Player* player, VipStatus_t status); + bool removeVIP(uint32_t vipGuid); + bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); + bool addVIPInternal(uint32_t vipGuid); + + //follow functions + bool setFollowCreature(Creature* creature) final; + void goToFollowCreature() final; + + //follow events + void onFollowCreature(const Creature* creature) final; + + //walk events + void onWalk(Direction& dir) final; + void onWalkAborted() final; + void onWalkComplete() final; + + void stopWalk(); + + void setChaseMode(chaseMode_t mode); + void setFightMode(fightMode_t mode) { + fightMode = mode; + } + void setSecureMode(bool mode) { + secureMode = mode; + } + + //combat functions + bool setAttackedCreature(Creature* creature) final; + bool isImmune(CombatType_t type) const final; + bool isImmune(ConditionType_t type) const final; + bool hasShield() const; + bool isAttackable() const final; + static bool lastHitIsPlayer(Creature* lastHitCreature); + + void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; + void changeMana(int32_t manaChange) final; + void changeSoul(int32_t soulChange); + + bool isPzLocked() const { + return pzLocked; + } + BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, + bool checkDefense = false, bool checkArmor = false, bool field = false) final; + void doAttacking(uint32_t interval) final; + bool hasExtraSwing() final { + return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); + } + + uint16_t getSkillLevel(uint8_t skill) const { + return std::max(0, skills[skill].level + varSkills[skill]); + } + uint16_t getBaseSkill(uint8_t skill) const { + return skills[skill].level; + } + uint8_t getSkillPercent(uint8_t skill) const { + return skills[skill].percent; + } + + bool getAddAttackSkill() const { + return addAttackSkillPoint; + } + BlockType_t getLastAttackBlockType() const { + return lastAttackBlockType; + } + + Item* getWeapon() const; + Item* getAmmunition() const; + void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; + + void drainHealth(Creature* attacker, int32_t damage) final; + void drainMana(Creature* attacker, int32_t manaLoss) final; + void addManaSpent(uint64_t amount); + void addSkillAdvance(skills_t skill, uint64_t count); + + int32_t getArmor() const final; + int32_t getDefense() final; + fightMode_t getFightMode() const; + + void addInFightTicks(bool pzlock = false); + + uint64_t getGainedExperience(Creature* attacker) const final; + + //combat event functions + void onAddCondition(ConditionType_t type) final; + void onAddCombatCondition(ConditionType_t type) final; + void onEndCondition(ConditionType_t type) final; + void onCombatRemoveCondition(Condition* condition) final; + void onAttackedCreature(Creature* target) final; + void onAttacked() final; + void onAttackedCreatureDrainHealth(Creature* target, int32_t points) final; + void onTargetCreatureGainHealth(Creature* target, int32_t points) final; + bool onKilledCreature(Creature* target, bool lastHit = true) final; + void onGainExperience(uint64_t gainExp, Creature* target) final; + void onGainSharedExperience(uint64_t gainExp); + void onAttackedCreatureBlockHit(BlockType_t blockType) final; + void onBlockHit() final; + void onChangeZone(ZoneType_t zone) final; + void onAttackedCreatureChangeZone(ZoneType_t zone) final; + void onIdleStatus() final; + void onPlacedCreature() final; + + void getCreatureLight(LightInfo& light) const final; + + Skulls_t getSkull() const final; + Skulls_t getSkullClient(const Creature* creature) const final; + time_t getPlayerKillerEnd() const { return playerKillerEnd; } + void setPlayerKillerEnd(time_t ticks) { playerKillerEnd = ticks; } + + bool hasAttacked(const Player* attacked) const; + void addAttacked(const Player* attacked); + void clearAttacked(); + void addUnjustifiedDead(const Player* attacked); + void sendCreatureSkull(const Creature* creature) const { + if (client) { + client->sendCreatureSkull(creature); + } + } + void checkSkullTicks(); + + bool canWear(uint32_t lookType) const; + + bool canLogout(); + + size_t getMaxVIPEntries() const; + size_t getMaxDepotItems() const; + + //tile + //send methods + void sendAddTileItem(const Tile* tile, const Position& pos, const Item* item) { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendAddTileItem(pos, item, stackpos); + } + } + } + void sendUpdateTileItem(const Tile* tile, const Position& pos, const Item* item) { + if (client) { + int32_t stackpos = tile->getStackposOfItem(this, item); + if (stackpos != -1) { + client->sendUpdateTileItem(pos, stackpos, item); + } + } + } + void sendRemoveTileThing(const Position& pos, int32_t stackpos) { + if (stackpos != -1 && client) { + client->sendRemoveTileThing(pos, stackpos); + } + } + void sendUpdateTile(const Tile* tile, const Position& pos) { + if (client) { + client->sendUpdateTile(tile, pos); + } + } + + void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { + if (client) { + client->sendAddCreature(creature, pos, creature->getTile()->getStackposOfCreature(this, creature), isLogin); + } + } + void sendCreatureMove(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) { + if (client) { + client->sendMoveCreature(creature, newPos, newStackPos, oldPos, oldStackPos, teleport); + } + } + void sendCreatureTurn(const Creature* creature) { + if (client && canSeeCreature(creature)) { + int32_t stackpos = creature->getTile()->getStackposOfCreature(this, creature); + if (stackpos != -1) { + client->sendCreatureTurn(creature, stackpos); + } + } + } + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr) { + if (client) { + client->sendCreatureSay(creature, type, text, pos); + } + } + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) { + if (client) { + client->sendPrivateMessage(speaker, type, text); + } + } + void sendCreatureSquare(const Creature* creature, SquareColor_t color) { + if (client) { + client->sendCreatureSquare(creature, color); + } + } + void sendCreatureChangeOutfit(const Creature* creature, const Outfit_t& outfit) { + if (client) { + client->sendCreatureOutfit(creature, outfit); + } + } + void sendCreatureChangeVisible(const Creature* creature, bool visible) { + if (!client) { + return; + } + + if (creature->getPlayer()) { + if (visible) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + static Outfit_t outfit; + client->sendCreatureOutfit(creature, outfit); + } + } else if (canSeeInvisibility()) { + client->sendCreatureOutfit(creature, creature->getCurrentOutfit()); + } else { + int32_t stackpos = creature->getTile()->getStackposOfCreature(this, creature); + if (stackpos == -1) { + return; + } + + if (visible) { + client->sendAddCreature(creature, creature->getPosition(), stackpos, false); + } else { + client->sendRemoveTileThing(creature->getPosition(), stackpos); + } + } + } + void sendCreatureLight(const Creature* creature) { + if (client) { + client->sendCreatureLight(creature); + } + } + void sendCreatureShield(const Creature* creature) { + if (client) { + client->sendCreatureShield(creature); + } + } + + //container + void sendAddContainerItem(const Container* container, const Item* item); + void sendUpdateContainerItem(const Container* container, uint16_t slot, const Item* newItem); + void sendRemoveContainerItem(const Container* container, uint16_t slot); + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) { + if (client) { + client->sendContainer(cid, container, hasParent, firstIndex); + } + } + + //inventory + void sendInventoryItem(slots_t slot, const Item* item) { + if (client) { + client->sendInventoryItem(slot, item); + } + } + + //event methods + void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, + const ItemType& oldType, const Item* newItem, const ItemType& newType) final; + void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, + const Item* item) final; + + void onCreatureAppear(Creature* creature, bool isLogin) final; + void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, + const Tile* oldTile, const Position& oldPos, bool teleport) final; + + void onAttackedCreatureDisappear(bool isLogout) final; + void onFollowCreatureDisappear(bool isLogout) final; + + //container + void onAddContainerItem(const Item* item); + void onUpdateContainerItem(const Container* container, const Item* oldItem, const Item* newItem); + void onRemoveContainerItem(const Container* container, const Item* item); + + void onCloseContainer(const Container* container); + void onSendContainer(const Container* container); + void autoCloseContainers(const Container* container); + + //inventory + void onUpdateInventoryItem(Item* oldItem, Item* newItem); + void onRemoveInventoryItem(Item* item); + + void sendCancelMessage(const std::string& msg) const { + if (client) { + client->sendTextMessage(TextMessage(MESSAGE_STATUS_SMALL, msg)); + } + } + void sendCancelMessage(ReturnValue message) const; + void sendCancelTarget() const { + if (client) { + client->sendCancelTarget(); + } + } + void sendCancelWalk() const { + if (client) { + client->sendCancelWalk(); + } + } + void sendChangeSpeed(const Creature* creature, uint32_t newSpeed) const { + if (client) { + client->sendChangeSpeed(creature, newSpeed); + } + } + void sendCreatureHealth(const Creature* creature) const { + if (client) { + client->sendCreatureHealth(creature); + } + } + void sendDistanceShoot(const Position& from, const Position& to, unsigned char type) const { + if (client) { + client->sendDistanceShoot(from, to, type); + } + } + void sendHouseWindow(House* house, uint32_t listId) const; + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendCreatePrivateChannel(channelId, channelName); + } + } + void sendClosePrivate(uint16_t channelId); + void sendIcons() const { + if (client) { + client->sendIcons(getClientIcons()); + } + } + void sendMagicEffect(const Position& pos, uint8_t type) const { + if (client) { + client->sendMagicEffect(pos, type); + } + } + void sendPing(); + void sendPingBack() const { + if (client) { + client->sendPingBack(); + } + } + void sendStats(); + void sendSkills() const { + if (client) { + client->sendSkills(); + } + } + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) const { + if (client) { + client->sendAnimatedText(pos, color, text); + } + } + void sendTextMessage(MessageClasses mclass, const std::string& message) const { + if (client) { + client->sendTextMessage(TextMessage(mclass, message)); + } + } + void sendTextMessage(const TextMessage& message) const { + if (client) { + client->sendTextMessage(message); + } + } + void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { + if (client) { + client->sendTextWindow(windowTextId, item, maxlen, canWrite); + } + } + void sendTextWindow(uint32_t itemId, const std::string& text) const { + if (client) { + client->sendTextWindow(windowTextId, itemId, text); + } + } + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) const { + if (client) { + client->sendToChannel(creature, type, text, channelId); + } + } + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const { + if (client) { + client->sendTradeItemRequest(traderName, item, ack); + } + } + void sendTradeClose() const { + if (client) { + client->sendCloseTrade(); + } + } + void sendWorldLight(const LightInfo& lightInfo) { + if (client) { + client->sendWorldLight(lightInfo); + } + } + void sendChannelsDialog() { + if (client) { + client->sendChannelsDialog(); + } + } + void sendOpenPrivateChannel(const std::string& receiver) { + if (client) { + client->sendOpenPrivateChannel(receiver); + } + } + void sendOutfitWindow() { + if (client) { + client->sendOutfitWindow(); + } + } + void sendCloseContainer(uint8_t cid) { + if (client) { + client->sendCloseContainer(cid); + } + } + void sendRemoveRuleViolationReport(const std::string& name) const { + if (client) { + client->sendRemoveRuleViolationReport(name); + } + } + void sendRuleViolationCancel(const std::string& name) const { + if (client) { + client->sendRuleViolationCancel(name); + } + } + void sendLockRuleViolationReport() const { + if (client) { + client->sendLockRuleViolation(); + } + } + void sendRuleViolationsChannel(uint16_t channelId) const { + if (client) { + client->sendRuleViolationsChannel(channelId); + } + } + + void sendChannel(uint16_t channelId, const std::string& channelName) { + if (client) { + client->sendChannel(channelId, channelName); + } + } + void sendFightModes() { + if (client) { + client->sendFightModes(); + } + } + void sendNetworkMessage(const NetworkMessage& message) { + if (client) { + client->writeToOutputBuffer(message); + } + } + + void receivePing() { + lastPong = OTSYS_TIME(); + } + + void onThink(uint32_t interval) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + void setNextAction(int64_t time) { + if (time > nextAction) { + nextAction = time; + } + } + bool canDoAction() const { + return nextAction <= OTSYS_TIME(); + } + uint32_t getNextActionTime() const; + + Item* getWriteItem(uint32_t& windowTextId, uint16_t& maxWriteLen); + void setWriteItem(Item* item, uint16_t maxWriteLen = 0); + + House* getEditHouse(uint32_t& windowTextId, uint32_t& listId); + void setEditHouse(House* house, uint32_t listId = 0); + + void learnInstantSpell(const std::string& spellName); + void forgetInstantSpell(const std::string& spellName); + bool hasLearnedInstantSpell(const std::string& spellName) const; + + protected: + std::forward_list getMuteConditions() const; + + void checkTradeState(const Item* item); + bool hasCapacity(const Item* item, uint32_t count) const; + + void gainExperience(uint64_t exp); + void addExperience(uint64_t exp, bool sendText = false, bool applyStages = true); + void removeExperience(uint64_t exp); + + void updateInventoryWeight(); + + void setNextWalkActionTask(SchedulerTask* task); + void setNextWalkTask(SchedulerTask* task); + void setNextActionTask(SchedulerTask* task); + + void dropLoot(Container* corpse, Creature*) final; + void death(Creature* lastHitCreature) final; + bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, + uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing*) final {} + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + std::map& getAllItemTypeCount(std::map& countMap) const final; + Thing* getThing(size_t index) const final; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + + uint32_t checkPlayerKilling(); + + std::unordered_set attackedSet; + std::unordered_set VIPList; + + std::map openContainers; + std::map depotLockerMap; + std::map storageMap; + + std::vector outfits; + GuildWarList guildWarList; + + std::forward_list invitePartyList; + std::forward_list learnedInstantSpellList; + std::forward_list storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow + + std::list murderTimeStamps; + + std::string name; + std::string guildNick; + + Skill skills[SKILL_LAST + 1]; + LightInfo itemsLight; + Position loginPosition; + Position lastWalkthroughPosition; + + time_t lastLoginSaved = 0; + time_t lastLogout = 0; + time_t playerKillerEnd = 0; + + uint64_t experience = 0; + uint64_t manaSpent = 0; + uint64_t bankBalance = 0; + int64_t lastAttack = 0; + int64_t lastFailedFollow = 0; + int64_t lastPing; + int64_t lastPong; + int64_t nextAction = 0; + int64_t earliestAttackTime = 0; + + BedItem* bedItem = nullptr; + Guild* guild = nullptr; + const GuildRank* guildRank = nullptr; + Group* group = nullptr; + Item* tradeItem = nullptr; + Item* inventory[CONST_SLOT_LAST + 1] = {}; + Item* writeItem = nullptr; + House* editHouse = nullptr; + Party* party = nullptr; + Player* tradePartner = nullptr; + ProtocolGame_ptr client; + SchedulerTask* walkTask = nullptr; + Town* town = nullptr; + Vocation* vocation = nullptr; + + uint32_t inventoryWeight = 0; + uint32_t capacity = 40000; + uint32_t damageImmunities = 0; + uint32_t conditionImmunities = 0; + uint32_t conditionSuppressions = 0; + uint32_t level = 1; + uint32_t magLevel = 0; + uint32_t actionTaskEvent = 0; + uint32_t nextStepEvent = 0; + uint32_t walkTaskEvent = 0; + uint32_t MessageBufferTicks = 0; + uint32_t lastIP = 0; + uint32_t accountNumber = 0; + uint32_t guid = 0; + uint32_t windowTextId = 0; + uint32_t editListId = 0; + uint32_t manaMax = 0; + int32_t varSkills[SKILL_LAST + 1] = {}; + int32_t varStats[STAT_LAST + 1] = {}; + int32_t MessageBufferCount = 0; + int32_t premiumDays = 0; + int32_t bloodHitCount = 0; + int32_t shieldBlockCount = 0; + int32_t idleTime = 0; + + uint16_t maxWriteLen = 0; + + uint8_t soul = 0; + uint8_t blessings = 0; + uint8_t levelPercent = 0; + uint8_t magLevelPercent = 0; + + PlayerSex_t sex = PLAYERSEX_FEMALE; + OperatingSystem_t operatingSystem = CLIENTOS_NONE; + BlockType_t lastAttackBlockType = BLOCK_NONE; + tradestate_t tradeState = TRADE_NONE; + chaseMode_t chaseMode = CHASEMODE_STANDSTILL; + fightMode_t fightMode = FIGHTMODE_ATTACK; + AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + + bool secureMode = false; + bool ghostMode = false; + bool pzLocked = false; + bool isConnecting = false; + bool addAttackSkillPoint = false; + bool inventoryAbilities[CONST_SLOT_LAST + 1] = {}; + + static uint32_t playerAutoID; + + void updateItemsLight(bool internal = false); + int32_t getStepSpeed() const final { + return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); + } + void updateBaseSpeed() { + if (!hasFlag(PlayerFlag_SetMaxSpeed)) { + baseSpeed = vocation->getBaseSpeed() + (level - 1); + } else { + baseSpeed = PLAYER_MAX_SPEED; + } + } + + bool isPromoted() const; + + uint32_t getAttackSpeed() const { + return vocation->getAttackSpeed(); + } + + static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); + static uint16_t getDropLootPercent(); + double getLostPercent() const; + uint64_t getLostExperience() const final { + return skillLoss ? static_cast(experience * getLostPercent()) : 0; + } + uint32_t getDamageImmunities() const final { + return damageImmunities; + } + uint32_t getConditionImmunities() const final { + return conditionImmunities; + } + uint32_t getConditionSuppressions() const final { + return conditionSuppressions; + } + uint16_t getLookCorpse() const final; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; + + friend class Game; + friend class Npc; + friend class LuaScriptInterface; + friend class Map; + friend class Actions; + friend class IOLoginData; + friend class ProtocolGame; + friend class BehaviourDatabase; + friend class ConjureSpell; +}; + +#endif diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 0000000..5dd6ff0 --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,73 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "position.h" + +std::ostream& operator<<(std::ostream& os, const Position& pos) +{ + os << "( " << std::setw(5) << std::setfill('0') << pos.x; + os << " / " << std::setw(5) << std::setfill('0') << pos.y; + os << " / " << std::setw(3) << std::setfill('0') << pos.getZ(); + os << " )"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const Direction& dir) +{ + switch (dir) { + case DIRECTION_NORTH: + os << "North"; + break; + + case DIRECTION_EAST: + os << "East"; + break; + + case DIRECTION_WEST: + os << "West"; + break; + + case DIRECTION_SOUTH: + os << "South"; + break; + + case DIRECTION_SOUTHWEST: + os << "South-West"; + break; + + case DIRECTION_SOUTHEAST: + os << "South-East"; + break; + + case DIRECTION_NORTHWEST: + os << "North-West"; + break; + + case DIRECTION_NORTHEAST: + os << "North-East"; + break; + + default: + break; + } + + return os; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 0000000..257f7e5 --- /dev/null +++ b/src/position.h @@ -0,0 +1,134 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 +#define FS_POSITION_H_5B684192F7034FB8857C8280D2CC6C75 + +enum Direction : uint8_t { + DIRECTION_NORTH = 0, + DIRECTION_EAST = 1, + DIRECTION_SOUTH = 2, + DIRECTION_WEST = 3, + + DIRECTION_DIAGONAL_MASK = 4, + DIRECTION_SOUTHWEST = DIRECTION_DIAGONAL_MASK | 0, + DIRECTION_SOUTHEAST = DIRECTION_DIAGONAL_MASK | 1, + DIRECTION_NORTHWEST = DIRECTION_DIAGONAL_MASK | 2, + DIRECTION_NORTHEAST = DIRECTION_DIAGONAL_MASK | 3, + + DIRECTION_LAST = DIRECTION_NORTHEAST, + DIRECTION_NONE = 8, +}; + +struct Position +{ + constexpr Position() = default; + constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {} + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; + } + + template + inline static bool areInRange(const Position& p1, const Position& p2) { + return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay && Position::getDistanceZ(p1, p2) <= deltaz; + } + + inline static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { + return p1.getX() - p2.getX(); + } + inline static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { + return p1.getY() - p2.getY(); + } + inline static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { + return p1.getZ() - p2.getZ(); + } + + inline static int32_t getDistanceX(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetX(p1, p2)); + } + inline static int32_t getDistanceY(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetY(p1, p2)); + } + inline static int16_t getDistanceZ(const Position& p1, const Position& p2) { + return std::abs(Position::getOffsetZ(p1, p2)); + } + + uint16_t x = 0; + uint16_t y = 0; + uint8_t z = 0; + + bool operator<(const Position& p) const { + if (z < p.z) { + return true; + } + + if (z > p.z) { + return false; + } + + if (y < p.y) { + return true; + } + + if (y > p.y) { + return false; + } + + if (x < p.x) { + return true; + } + + if (x > p.x) { + return false; + } + + return false; + } + + bool operator>(const Position& p) const { + return ! (*this < p); + } + + bool operator==(const Position& p) const { + return p.x == x && p.y == y && p.z == z; + } + + bool operator!=(const Position& p) const { + return p.x != x || p.y != y || p.z != z; + } + + Position operator+(const Position& p1) const { + return Position(x + p1.x, y + p1.y, z + p1.z); + } + + Position operator-(const Position& p1) const { + return Position(x - p1.x, y - p1.y, z - p1.z); + } + + inline int_fast32_t getX() const { return x; } + inline int_fast32_t getY() const { return y; } + inline int_fast16_t getZ() const { return z; } +}; + +std::ostream& operator<<(std::ostream&, const Position&); +std::ostream& operator<<(std::ostream&, const Direction&); + +#endif diff --git a/src/protocol.cpp b/src/protocol.cpp new file mode 100644 index 0000000..ec871e7 --- /dev/null +++ b/src/protocol.cpp @@ -0,0 +1,154 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocol.h" +#include "outputmessage.h" +#include "rsa.h" + +extern RSA g_RSA; + +void Protocol::onSendMessage(const OutputMessage_ptr& msg) const +{ + if (!rawMessages) { + msg->writeMessageLength(); + + if (encryptionEnabled) { + XTEA_encrypt(*msg); + msg->addCryptoHeader(); + } + } +} + +void Protocol::onRecvMessage(NetworkMessage& msg) +{ + if (encryptionEnabled && !XTEA_decrypt(msg)) { + return; + } + + parsePacket(msg); +} + +OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) +{ + //dispatcher thread + if (!outputBuffer) { + outputBuffer = OutputMessagePool::getOutputMessage(); + } else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { + send(outputBuffer); + outputBuffer = OutputMessagePool::getOutputMessage(); + } + return outputBuffer; +} + +void Protocol::XTEA_encrypt(OutputMessage& msg) const +{ + const uint32_t delta = 0x61C88647; + + // The message must be a multiple of 8 + size_t paddingBytes = msg.getLength() % 8; + if (paddingBytes != 0) { + msg.addPaddingBytes(8 - paddingBytes); + } + + uint8_t* buffer = msg.getOutputBuffer(); + const size_t messageLength = msg.getLength(); + size_t readPos = 0; + const uint32_t k[] = {key[0], key[1], key[2], key[3]}; + while (readPos < messageLength) { + uint32_t v0; + memcpy(&v0, buffer + readPos, 4); + uint32_t v1; + memcpy(&v1, buffer + readPos + 4, 4); + + uint32_t sum = 0; + + for (int32_t i = 32; --i >= 0;) { + v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + sum -= delta; + v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]); + } + + memcpy(buffer + readPos, &v0, 4); + readPos += 4; + memcpy(buffer + readPos, &v1, 4); + readPos += 4; + } +} + +bool Protocol::XTEA_decrypt(NetworkMessage& msg) const +{ + if (((msg.getLength() - 2) % 8) != 0) { + return false; + } + + const uint32_t delta = 0x61C88647; + + uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); + const size_t messageLength = (msg.getLength() - 6); + size_t readPos = 0; + const uint32_t k[] = {key[0], key[1], key[2], key[3]}; + while (readPos < messageLength) { + uint32_t v0; + memcpy(&v0, buffer + readPos, 4); + uint32_t v1; + memcpy(&v1, buffer + readPos + 4, 4); + + uint32_t sum = 0xC6EF3720; + + for (int32_t i = 32; --i >= 0;) { + v1 -= ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[(sum >> 11) & 3]); + sum += delta; + v0 -= ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]); + } + + memcpy(buffer + readPos, &v0, 4); + readPos += 4; + memcpy(buffer + readPos, &v1, 4); + readPos += 4; + } + + int innerLength = msg.get(); + if (innerLength > msg.getLength() - 4) { + return false; + } + + msg.setLength(innerLength); + return true; +} + +bool Protocol::RSA_decrypt(NetworkMessage& msg) +{ + if ((msg.getLength() - msg.getBufferPosition()) < 128) { + return false; + } + + g_RSA.decrypt(reinterpret_cast(msg.getBuffer()) + msg.getBufferPosition()); //does not break strict aliasing + return msg.getByte() == 0; +} + +uint32_t Protocol::getIP() const +{ + if (auto connection = getConnection()) { + return connection->getIP(); + } + + return 0; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..8063956 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,97 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 +#define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 + +#include "connection.h" + +class Protocol : public std::enable_shared_from_this +{ + public: + explicit Protocol(Connection_ptr connection) : connection(connection) {} + virtual ~Protocol() = default; + + // non-copyable + Protocol(const Protocol&) = delete; + Protocol& operator=(const Protocol&) = delete; + + virtual void parsePacket(NetworkMessage&) {} + + virtual void onSendMessage(const OutputMessage_ptr& msg) const; + void onRecvMessage(NetworkMessage& msg); + virtual void onRecvFirstMessage(NetworkMessage& msg) = 0; + virtual void onConnect() {} + + bool isConnectionExpired() const { + return connection.expired(); + } + + Connection_ptr getConnection() const { + return connection.lock(); + } + + uint32_t getIP() const; + + //Use this function for autosend messages only + OutputMessage_ptr getOutputBuffer(int32_t size); + + OutputMessage_ptr& getCurrentBuffer() { + return outputBuffer; + } + + void send(OutputMessage_ptr msg) const { + if (auto connection = getConnection()) { + connection->send(msg); + } + } + + protected: + void disconnect() const { + if (auto connection = getConnection()) { + connection->close(); + } + } + void enableXTEAEncryption() { + encryptionEnabled = true; + } + void setXTEAKey(const uint32_t* key) { + memcpy(this->key, key, sizeof(*key) * 4); + } + + void XTEA_encrypt(OutputMessage& msg) const; + bool XTEA_decrypt(NetworkMessage& msg) const; + static bool RSA_decrypt(NetworkMessage& msg); + + void setRawMessages(bool value) { + rawMessages = value; + } + + virtual void release() {} + friend class Connection; + + OutputMessage_ptr outputBuffer; + private: + const ConnectionWeak_ptr connection; + uint32_t key[4] = {}; + bool encryptionEnabled = false; + bool rawMessages = false; +}; + +#endif diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp new file mode 100644 index 0000000..48907fc --- /dev/null +++ b/src/protocolgame.cpp @@ -0,0 +1,2026 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "protocolgame.h" + +#include "outputmessage.h" + +#include "player.h" + +#include "configmanager.h" +#include "actions.h" +#include "game.h" +#include "iologindata.h" +#include "waitlist.h" +#include "ban.h" +#include "scheduler.h" + +extern ConfigManager g_config; +extern Actions actions; +extern CreatureEvents* g_creatureEvents; +extern Chat* g_chat; + +void ProtocolGame::release() +{ + //dispatcher thread + if (player && player->client == shared_from_this()) { + player->client.reset(); + player->decrementReferenceCounter(); + player = nullptr; + } + + OutputMessagePool::getInstance().removeProtocolFromAutosend(shared_from_this()); + Protocol::release(); +} + +void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem) +{ + //dispatcher thread + Player* foundPlayer = g_game.getPlayerByName(name); + if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) { + player = new Player(getThis()); + player->setName(name); + + player->incrementReferenceCounter(); + player->setID(); + + if (!IOLoginData::preloadPlayer(player, name)) { + disconnectClient("Your character could not be loaded."); + return; + } + + if (IOBan::isPlayerNamelocked(player->getGUID())) { + disconnectClient("Your character has been namelocked."); + return; + } + + if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient("The game is just going down.\nPlease try again later."); + return; + } + + if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) { + disconnectClient("Server is currently closed.\nPlease try again later."); + return; + } + + if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) { + disconnectClient("You may only login with one character\nof your account at the same time."); + return; + } + + if (!player->hasFlag(PlayerFlag_CannotBeBanned)) { + BanInfo banInfo; + if (IOBan::isAccountBanned(accountId, banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + if (banInfo.expiresAt > 0) { + ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + } else { + ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + } + disconnectClient(ss.str()); + return; + } + } + + if (!WaitingList::getInstance()->clientLogin(player)) { + uint32_t currentSlot = WaitingList::getInstance()->getClientSlot(player); + uint32_t retryTime = WaitingList::getTime(currentSlot); + std::ostringstream ss; + + ss << "Too many players online.\nYou are at place " + << currentSlot << " on the waiting list."; + + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x16); + output->addString(ss.str()); + output->addByte(retryTime); + send(output); + disconnect(); + return; + } + + if (!IOLoginData::loadPlayerByName(player, name)) { + disconnectClient("Your character could not be loaded."); + return; + } + + player->setOperatingSystem(operatingSystem); + + if (!g_game.placeCreature(player, player->getLoginPosition())) { + if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) { + disconnectClient("Temple position is wrong. Contact the administrator."); + return; + } + } + + if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { + player->registerCreatureEvent("ExtendedOpcode"); + } + + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); + acceptPackets = true; + } else { + if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) { + //Already trying to connect + disconnectClient("You are already logged in."); + return; + } + + if (foundPlayer->client) { + foundPlayer->disconnect(); + foundPlayer->isConnecting = true; + + eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getID(), operatingSystem))); + } else { + connect(foundPlayer->getID(), operatingSystem); + } + } + OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this()); +} + +void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) +{ + eventConnect = 0; + + Player* foundPlayer = g_game.getPlayerByID(playerId); + if (!foundPlayer || foundPlayer->client) { + disconnectClient("You are already logged in."); + return; + } + + if (isConnectionExpired()) { + //ProtocolGame::release() has been called at this point and the Connection object + //no longer exists, so we return to prevent leakage of the Player. + return; + } + + player = foundPlayer; + player->incrementReferenceCounter(); + + g_chat->removeUserFromAllChannels(*player); + player->setOperatingSystem(operatingSystem); + player->isConnecting = false; + + player->client = getThis(); + sendAddCreature(player, player->getPosition(), 0, false); + player->lastIP = player->getIP(); + player->lastLoginSaved = std::max(time(nullptr), player->lastLoginSaved + 1); + acceptPackets = true; +} + +void ProtocolGame::logout(bool displayEffect, bool forced) +{ + //dispatcher thread + if (!player) { + return; + } + + if (!player->isRemoved()) { + if (!forced) { + if (!player->isAccessPlayer()) { + if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) { + player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE); + return; + } + + if (player->hasCondition(CONDITION_INFIGHT)) { + player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT); + return; + } + } + + //scripting event - onLogout + if (!g_creatureEvents->playerLogout(player)) { + //Let the script handle the error message + return; + } + } + + if (displayEffect && player->getHealth() > 0) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + } + + disconnect(); + + g_game.removeCreature(player); +} + +void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + disconnect(); + return; + } + + OperatingSystem_t operatingSystem = static_cast(msg.get()); + version = msg.get(); + + if (!Protocol::RSA_decrypt(msg)) { + disconnect(); + return; + } + + uint32_t key[4]; + key[0] = msg.get(); + key[1] = msg.get(); + key[2] = msg.get(); + key[3] = msg.get(); + enableXTEAEncryption(); + setXTEAKey(key); + + if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { + NetworkMessage opcodeMessage; + opcodeMessage.addByte(0x32); + opcodeMessage.addByte(0x00); + opcodeMessage.add(0x00); + writeToOutputBuffer(opcodeMessage); + } + + msg.skipBytes(1); // gamemaster flag + + uint32_t accountNumber = msg.get(); + std::string characterName = msg.getString(); + std::string password = msg.getString(); + + /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + //sendUpdateRequest(); + disconnectClient("Use Tibia 7.72 to login!"); + return; + }*/ + + if (g_game.getGameState() == GAME_STATE_STARTUP) { + disconnectClient("Gameworld is starting up. Please wait."); + return; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient("Gameworld is under maintenance. Please re-connect in a while."); + return; + } + + BanInfo banInfo; + if (IOBan::isIpBanned(getIP(), banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient(ss.str()); + return; + } + + uint32_t accountId = IOLoginData::gameworldAuthentication(accountNumber, password, characterName); + if (accountId == 0) { + disconnectClient("Account number or password is not correct."); + return; + } + + Account account; + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Account number or password is not correct."); + return; + } + + //Update premium days + Game::updatePremium(account); + + g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem))); +} + +void ProtocolGame::sendUpdateRequest() +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x11); + send(output); + disconnect(); +} + +void ProtocolGame::disconnectClient(const std::string& message) const +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x14); + output->addString(message); + send(output); + disconnect(); +} + +void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg) +{ + auto out = getOutputBuffer(msg.getLength()); + out->append(msg); +} + +void ProtocolGame::parsePacket(NetworkMessage& msg) +{ + if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getLength() <= 0) { + return; + } + + uint8_t recvbyte = msg.getByte(); + + if (!player) { + if (recvbyte == 0x0F) { + disconnect(); + } + + return; + } + + //a dead player can not performs actions + if (player->isRemoved() || player->getHealth() <= 0) { + if (recvbyte == 0x0F) { + disconnect(); + return; + } + + if (recvbyte != 0x14) { + return; + } + } + + switch (recvbyte) { + case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break; + case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break; + case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break; + case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode + case 0x64: parseAutoWalk(msg); break; + case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break; + case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break; + case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break; + case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break; + case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break; + case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break; + case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break; + case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break; + case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break; + case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break; + case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; + case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; + case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; + case 0x78: parseThrow(msg); break; + case 0x7D: parseRequestTrade(msg); break; + case 0x7E: parseLookInTrade(msg); break; + case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break; + case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break; + case 0x82: parseUseItem(msg); break; + case 0x83: parseUseItemEx(msg); break; + case 0x84: parseUseWithCreature(msg); break; + case 0x85: parseRotateItem(msg); break; + case 0x87: parseCloseContainer(msg); break; + case 0x88: parseUpArrowContainer(msg); break; + case 0x89: parseTextWindow(msg); break; + case 0x8A: parseHouseWindow(msg); break; + case 0x8C: parseLookAt(msg); break; + case 0x8D: parseLookInBattleList(msg); break; + case 0x96: parseSay(msg); break; + case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; + case 0x98: parseOpenChannel(msg); break; + case 0x99: parseCloseChannel(msg); break; + case 0x9A: parseOpenPrivateChannel(msg); break; + case 0x9B: parseProcessRuleViolationReport(msg); break; + case 0x9C: parseCloseRuleViolationReport(msg); break; + case 0x9D: addGameTask(&Game::playerCancelRuleViolationReport, player->getID()); break; + case 0xA0: parseFightModes(msg); break; + case 0xA1: parseAttack(msg); break; + case 0xA2: parseFollow(msg); break; + case 0xA3: parseInviteToParty(msg); break; + case 0xA4: parseJoinParty(msg); break; + case 0xA5: parseRevokePartyInvite(msg); break; + case 0xA6: parsePassPartyLeadership(msg); break; + case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break; + case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; + case 0xAB: parseChannelInvite(msg); break; + case 0xAC: parseChannelExclude(msg); break; + case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; + case 0xC9: /* update tile */ break; + case 0xCA: parseUpdateContainer(msg); break; + case 0xCC: parseSeekInContainer(msg); break; + case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; + case 0xD3: parseSetOutfit(msg); break; + case 0xDC: parseAddVip(msg); break; + case 0xDD: parseRemoveVip(msg); break; + case 0xE6: parseBugReport(msg); break; + case 0xE7: /* violation window */ break; + case 0xE8: parseDebugAssert(msg); break; + default: + std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; + break; + } + + if (msg.isOverrun()) { + disconnect(); + } +} + +void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) +{ + int32_t count; + Item* ground = tile->getGround(); + if (ground) { + msg.addItem(ground); + count = 1; + } else { + count = 0; + } + + const TileItemVector* items = tile->getItemList(); + if (items) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + msg.addItem(*it); + + if (++count == 10) { + return; + } + } + } + + const CreatureVector* creatures = tile->getCreatures(); + if (creatures) { + for (const Creature* creature : boost::adaptors::reverse(*creatures)) { + if (!player->canSeeCreature(creature)) { + continue; + } + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, known, removedKnown); + + if (++count == 10) { + return; + } + } + } + + if (items) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + msg.addItem(*it); + + if (++count == 10) { + return; + } + } + } +} + +void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg) +{ + int32_t skip = -1; + int32_t startz, endz, zstep; + + if (z > 7) { + startz = z - 2; + endz = std::min(MAP_MAX_LAYERS - 1, z + 2); + zstep = 1; + } else { + startz = 7; + endz = 0; + zstep = -1; + } + + for (int32_t nz = startz; nz != endz + zstep; nz += zstep) { + GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip); + } + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } +} + +void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, int32_t offset, int32_t& skip) +{ + for (int32_t nx = 0; nx < width; nx++) { + for (int32_t ny = 0; ny < height; ny++) { + Tile* tile = g_game.map.getTile(x + nx + offset, y + ny + offset, z); + if (tile) { + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + + skip = 0; + GetTileDescription(tile, msg); + } else if (skip == 0xFE) { + msg.addByte(0xFF); + msg.addByte(0xFF); + skip = -1; + } else { + ++skip; + } + } + } +} + +void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown) +{ + auto result = knownCreatureSet.insert(id); + if (!result.second) { + known = true; + return; + } + + known = false; + + if (knownCreatureSet.size() > 150) { + // Look for a creature to remove + for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) { + Creature* creature = g_game.getCreatureByID(*it); + if (!canSee(creature)) { + removedKnown = *it; + knownCreatureSet.erase(it); + return; + } + } + + // Bad situation. Let's just remove anyone. + auto it = knownCreatureSet.begin(); + if (*it == id) { + ++it; + } + + removedKnown = *it; + knownCreatureSet.erase(it); + } else { + removedKnown = 0; + } +} + +bool ProtocolGame::canSee(const Creature* c) const +{ + if (!c || !player || c->isRemoved()) { + return false; + } + + if (!player->canSeeCreature(c)) { + return false; + } + + return canSee(c->getPosition()); +} + +bool ProtocolGame::canSee(const Position& pos) const +{ + return canSee(pos.x, pos.y, pos.z); +} + +bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const +{ + if (!player) { + return false; + } + + const Position& myPos = player->getPosition(); + if (myPos.z <= 7) { + //we are on ground level or above (7 -> 0) + //view is from 7 -> 0 + if (z > 7) { + return false; + } + } else if (myPos.z >= 8) { + //we are underground (8 -> 15) + //view is +/- 2 from the floor we stand on + if (std::abs(myPos.getZ() - z) > 2) { + return false; + } + } + + //negative offset means that the action taken place is on a lower floor than ourself + int32_t offsetz = myPos.getZ() - z; + if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) && + (y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) { + return true; + } + return false; +} + +// Parse methods +void ProtocolGame::parseChannelInvite(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerChannelInvite, player->getID(), name); +} + +void ProtocolGame::parseChannelExclude(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerChannelExclude, player->getID(), name); +} + +void ProtocolGame::parseOpenChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.get(); + addGameTask(&Game::playerOpenChannel, player->getID(), channelId); +} + +void ProtocolGame::parseCloseChannel(NetworkMessage& msg) +{ + uint16_t channelId = msg.get(); + addGameTask(&Game::playerCloseChannel, player->getID(), channelId); +} + +void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) +{ + const std::string receiver = msg.getString(); + addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver); +} + +void ProtocolGame::parseAutoWalk(NetworkMessage& msg) +{ + uint8_t numdirs = msg.getByte(); + if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 4)) { + return; + } + + msg.skipBytes(numdirs); + + std::forward_list path; + for (uint8_t i = 0; i < numdirs; ++i) { + uint8_t rawdir = msg.getPreviousByte(); + switch (rawdir) { + case 1: path.push_front(DIRECTION_EAST); break; + case 2: path.push_front(DIRECTION_NORTHEAST); break; + case 3: path.push_front(DIRECTION_NORTH); break; + case 4: path.push_front(DIRECTION_NORTHWEST); break; + case 5: path.push_front(DIRECTION_WEST); break; + case 6: path.push_front(DIRECTION_SOUTHWEST); break; + case 7: path.push_front(DIRECTION_SOUTH); break; + case 8: path.push_front(DIRECTION_SOUTHEAST); break; + default: break; + } + } + + if (path.empty()) { + return; + } + + addGameTask(&Game::playerAutoWalk, player->getID(), path); +} + +void ProtocolGame::parseSetOutfit(NetworkMessage& msg) +{ + Outfit_t newOutfit; + newOutfit.lookType = msg.get(); + newOutfit.lookHead = msg.getByte(); + newOutfit.lookBody = msg.getByte(); + newOutfit.lookLegs = msg.getByte(); + newOutfit.lookFeet = msg.getByte(); + addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); +} + +void ProtocolGame::parseUseItem(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + uint8_t index = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId); +} + +void ProtocolGame::parseUseItemEx(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t fromSpriteId = msg.get(); + uint8_t fromStackPos = msg.getByte(); + Position toPos = msg.getPosition(); + uint16_t toSpriteId = msg.get(); + uint8_t toStackPos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId); +} + +void ProtocolGame::parseUseWithCreature(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t fromStackPos = msg.getByte(); + uint32_t creatureId = msg.get(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId); +} + +void ProtocolGame::parseCloseContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerCloseContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerMoveUpContainer, player->getID(), cid); +} + +void ProtocolGame::parseUpdateContainer(NetworkMessage& msg) +{ + uint8_t cid = msg.getByte(); + addGameTask(&Game::playerUpdateContainer, player->getID(), cid); +} + +void ProtocolGame::parseThrow(NetworkMessage& msg) +{ + Position fromPos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t fromStackpos = msg.getByte(); + Position toPos = msg.getPosition(); + uint8_t count = msg.getByte(); + + if (toPos != fromPos) { + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count); + } +} + +void ProtocolGame::parseLookAt(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + msg.skipBytes(2); // spriteId + uint8_t stackpos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, stackpos); +} + +void ProtocolGame::parseLookInBattleList(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId); +} + +void ProtocolGame::parseSay(NetworkMessage& msg) +{ + std::string receiver; + uint16_t channelId; + + SpeakClasses type = static_cast(msg.getByte()); + switch (type) { + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: + receiver = msg.getString(); + channelId = 0; + break; + + case TALKTYPE_CHANNEL_Y: + case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: + channelId = msg.get(); + break; + + default: + channelId = 0; + break; + } + + const std::string text = msg.getString(); + if (text.length() > 255 || text.find('\n') != std::string::npos) { + return; + } + + addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text); +} + +void ProtocolGame::parseFightModes(NetworkMessage& msg) +{ + uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive + uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fightning, 1 - chase opponent + uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked + // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 + + chaseMode_t chaseMode; + if (rawChaseMode == 1) { + chaseMode = CHASEMODE_FOLLOW; + } else { + chaseMode = CHASEMODE_STANDSTILL; + } + + fightMode_t fightMode; + if (rawFightMode == 1) { + fightMode = FIGHTMODE_ATTACK; + } else if (rawFightMode == 2) { + fightMode = FIGHTMODE_BALANCED; + } else { + fightMode = FIGHTMODE_DEFENSE; + } + + addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, chaseMode, rawSecureMode != 0); +} + +void ProtocolGame::parseAttack(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseFollow(NetworkMessage& msg) +{ + uint32_t creatureId = msg.get(); + addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); +} + +void ProtocolGame::parseProcessRuleViolationReport(NetworkMessage& msg) +{ + const std::string reporter = msg.getString(); + addGameTask(&Game::playerProcessRuleViolationReport, player->getID(), reporter); +} + +void ProtocolGame::parseCloseRuleViolationReport(NetworkMessage& msg) +{ + const std::string reporter = msg.getString(); + addGameTask(&Game::playerCloseRuleViolationReport, player->getID(), reporter); +} + +void ProtocolGame::parseTextWindow(NetworkMessage& msg) +{ + uint32_t windowTextId = msg.get(); + const std::string newText = msg.getString(); + addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText); +} + +void ProtocolGame::parseHouseWindow(NetworkMessage& msg) +{ + uint8_t doorId = msg.getByte(); + uint32_t id = msg.get(); + const std::string text = msg.getString(); + addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); +} + +void ProtocolGame::parseRequestTrade(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + uint32_t playerId = msg.get(); + addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId); +} + +void ProtocolGame::parseLookInTrade(NetworkMessage& msg) +{ + bool counterOffer = (msg.getByte() == 0x01); + uint8_t index = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index); +} + +void ProtocolGame::parseAddVip(NetworkMessage& msg) +{ + const std::string name = msg.getString(); + addGameTask(&Game::playerRequestAddVip, player->getID(), name); +} + +void ProtocolGame::parseRemoveVip(NetworkMessage& msg) +{ + uint32_t guid = msg.get(); + addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); +} + +void ProtocolGame::parseRotateItem(NetworkMessage& msg) +{ + Position pos = msg.getPosition(); + uint16_t spriteId = msg.get(); + uint8_t stackpos = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); +} + +void ProtocolGame::parseBugReport(NetworkMessage& msg) +{ + std::string bug = msg.getString(); + addGameTask(&Game::playerReportBug, player->getID(), bug); +} + +void ProtocolGame::parseDebugAssert(NetworkMessage& msg) +{ + if (debugAssertSent) { + return; + } + + debugAssertSent = true; + + std::string assertLine = msg.getString(); + std::string date = msg.getString(); + std::string description = msg.getString(); + std::string comment = msg.getString(); + addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment); +} + +void ProtocolGame::parseInviteToParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerInviteToParty, player->getID(), targetId); +} + +void ProtocolGame::parseJoinParty(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerJoinParty, player->getID(), targetId); +} + +void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId); +} + +void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) +{ + uint32_t targetId = msg.get(); + addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); +} + +void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) +{ + uint8_t containerId = msg.getByte(); + uint16_t index = msg.get(); + addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index); +} + +// Send methods +void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) +{ + NetworkMessage msg; + msg.addByte(0xAD); + msg.addString(receiver); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x8E); + msg.add(creature->getID()); + AddOutfit(msg, outfit); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureLight(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + AddCreatureLight(msg, creature); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendWorldLight(const LightInfo& lightInfo) +{ + NetworkMessage msg; + AddWorldLight(msg, lightInfo); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureShield(const Creature* creature) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x91); + msg.add(creature->getID()); + msg.addByte(player->getPartyShield(creature->getPlayer())); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSkull(const Creature* creature) +{ + if (g_game.getWorldType() != WORLD_TYPE_PVP) { + return; + } + + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x90); + msg.add(creature->getID()); + msg.addByte(player->getSkullClient(creature)); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x86); + msg.add(creature->getID()); + msg.addByte(color); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveRuleViolationReport(const std::string& name) +{ + NetworkMessage msg; + msg.addByte(0xAF); + msg.addString(name); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendLockRuleViolation() +{ + NetworkMessage msg; + msg.addByte(0xB1); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRuleViolationCancel(const std::string& name) +{ + NetworkMessage msg; + msg.addByte(0xB0); + msg.addString(name); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRuleViolationsChannel(uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xAE); + msg.add(channelId); + auto it = g_game.getRuleViolationReports().begin(); + for (; it != g_game.getRuleViolationReports().end(); ++it) { + const RuleViolation& rvr = it->second; + if (rvr.pending) { + Player* reporter = g_game.getPlayerByID(rvr.reporterId); + if (reporter) { + msg.addByte(0xAA); + msg.add(0); + msg.addString(reporter->getName()); + msg.addByte(TALKTYPE_RVR_CHANNEL); + msg.add(0); + msg.addString(rvr.text); + } + } + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendStats() +{ + NetworkMessage msg; + AddPlayerStats(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextMessage(const TextMessage& message) +{ + NetworkMessage msg; + msg.addByte(0xB4); + msg.addByte(message.type); + msg.addString(message.text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x84); + msg.addPosition(pos); + msg.addByte(color); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendClosePrivate(uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xB3); + msg.add(channelId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.addByte(0xB2); + msg.add(channelId); + msg.addString(channelName); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannelsDialog() +{ + NetworkMessage msg; + msg.addByte(0xAB); + + const ChannelList& list = g_chat->getChannelList(*player); + msg.addByte(list.size()); + for (ChatChannel* channel : list) { + msg.add(channel->getId()); + msg.addString(channel->getName()); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName) +{ + NetworkMessage msg; + msg.addByte(0xAC); + + msg.add(channelId); + msg.addString(channelName); + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendIcons(uint16_t icons) +{ + NetworkMessage msg; + msg.addByte(0xA2); + msg.addByte(static_cast(icons)); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex) +{ + NetworkMessage msg; + msg.addByte(0x6E); + + msg.addByte(cid); + + msg.addItem(container); + msg.addString(container->getName()); + + msg.addByte(container->capacity()); + + msg.addByte(hasParent ? 0x01 : 0x00); + + uint32_t containerSize = container->size(); + if (firstIndex < containerSize) { + uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), std::numeric_limits::max()); + + msg.addByte(itemsToSend); + for (auto it = container->getItemList().begin() + firstIndex, end = it + itemsToSend; it != end; ++it) { + msg.addItem(*it); + } + } else { + msg.addByte(0x00); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) +{ + NetworkMessage msg; + + if (ack) { + msg.addByte(0x7D); + } else { + msg.addByte(0x7E); + } + + msg.addString(traderName); + + if (const Container* tradeContainer = item->getContainer()) { + std::list listContainer {tradeContainer}; + std::list itemList {tradeContainer}; + while (!listContainer.empty()) { + const Container* container = listContainer.front(); + listContainer.pop_front(); + + for (Item* containerItem : container->getItemList()) { + Container* tmpContainer = containerItem->getContainer(); + if (tmpContainer) { + listContainer.push_back(tmpContainer); + } + itemList.push_back(containerItem); + } + } + + msg.addByte(itemList.size()); + for (const Item* listItem : itemList) { + msg.addItem(listItem); + } + } else { + msg.addByte(0x01); + msg.addItem(item); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseTrade() +{ + NetworkMessage msg; + msg.addByte(0x7F); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseContainer(uint8_t cid) +{ + NetworkMessage msg; + msg.addByte(0x6F); + msg.addByte(cid); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6B); + msg.addPosition(creature->getPosition()); + msg.addByte(stackPos); + msg.add(0x63); + msg.add(creature->getID()); + msg.addByte(creature->getDirection()); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/) +{ + NetworkMessage msg; + AddCreatureSpeak(msg, creature, type, text, 0, pos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId) +{ + NetworkMessage msg; + AddCreatureSpeak(msg, creature, type, text, channelId); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0xAA); + static uint32_t statementId = 0; + msg.add(++statementId); + if (type == TALKTYPE_RVR_ANSWER) { + msg.addString("Gamemaster"); + } else { + if (speaker) { + msg.addString(speaker->getName()); + } else { + msg.add(0x00); + } + } + + msg.addByte(type); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelTarget() +{ + NetworkMessage msg; + msg.addByte(0xA3); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed) +{ + NetworkMessage msg; + msg.addByte(0x8F); + msg.add(creature->getID()); + msg.add(speed); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCancelWalk() +{ + NetworkMessage msg; + msg.addByte(0xB5); + msg.addByte(player->getDirection()); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSkills() +{ + NetworkMessage msg; + AddPlayerSkills(msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPing() +{ + NetworkMessage msg; + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(0x1D); + } else { + // classic clients ping + msg.addByte(0x1E); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPingBack() +{ + NetworkMessage msg; + msg.addByte(0x1E); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type) +{ + NetworkMessage msg; + msg.addByte(0x85); + msg.addPosition(from); + msg.addPosition(to); + msg.addByte(type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x83); + msg.addPosition(pos); + msg.addByte(type); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureHealth(const Creature* creature) +{ + NetworkMessage msg; + msg.addByte(0x8C); + msg.add(creature->getID()); + msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + writeToOutputBuffer(msg); +} + +//tile +void ProtocolGame::sendMapDescription(const Position& pos) +{ + NetworkMessage msg; + msg.addByte(0x64); + msg.addPosition(player->getPosition()); + GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6A); + msg.addPosition(pos); + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x6B); + msg.addPosition(pos); + msg.addByte(stackpos); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveTileThing(const Position& pos, uint32_t stackpos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + RemoveTileThing(msg, pos, stackpos); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) +{ + if (!canSee(pos)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x69); + msg.addPosition(pos); + + if (tile) { + GetTileDescription(tile, msg); + msg.addByte(0x00); + msg.addByte(0xFF); + } else { + msg.addByte(0x01); + msg.addByte(0xFF); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendFightModes() +{ + NetworkMessage msg; + msg.addByte(0xA7); + msg.addByte(player->fightMode); + msg.addByte(player->chaseMode); + msg.addByte(player->secureMode); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin) +{ + if (!canSee(pos)) { + return; + } + + if (creature != player) { + if (stackpos != -1) { + NetworkMessage msg; + msg.addByte(0x6A); + msg.addPosition(pos); + + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } + + bool known; + uint32_t removedKnown; + checkCreatureAsKnown(creature->getID(), known, removedKnown); + AddCreature(msg, creature, known, removedKnown); + writeToOutputBuffer(msg); + } + + if (isLogin) { + sendMagicEffect(pos, CONST_ME_TELEPORT); + } + return; + } + + NetworkMessage msg; + msg.addByte(0x0A); + + msg.add(player->getID()); + msg.add(0x32); // beat duration (50) + + // can report bugs? + if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { + msg.addByte(0x01); + } else { + msg.addByte(0x00); + } + + if (player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + msg.addByte(0x0B); + for (uint8_t i = 0; i < 32; i++) { + msg.addByte(0xFF); + } + } + + writeToOutputBuffer(msg); + + sendMapDescription(pos); + + if (isLogin) { + sendMagicEffect(pos, CONST_ME_TELEPORT); + } + + sendInventoryItem(CONST_SLOT_HEAD, player->getInventoryItem(CONST_SLOT_HEAD)); + sendInventoryItem(CONST_SLOT_NECKLACE, player->getInventoryItem(CONST_SLOT_NECKLACE)); + sendInventoryItem(CONST_SLOT_BACKPACK, player->getInventoryItem(CONST_SLOT_BACKPACK)); + sendInventoryItem(CONST_SLOT_ARMOR, player->getInventoryItem(CONST_SLOT_ARMOR)); + sendInventoryItem(CONST_SLOT_RIGHT, player->getInventoryItem(CONST_SLOT_RIGHT)); + sendInventoryItem(CONST_SLOT_LEFT, player->getInventoryItem(CONST_SLOT_LEFT)); + sendInventoryItem(CONST_SLOT_LEGS, player->getInventoryItem(CONST_SLOT_LEGS)); + sendInventoryItem(CONST_SLOT_FEET, player->getInventoryItem(CONST_SLOT_FEET)); + sendInventoryItem(CONST_SLOT_RING, player->getInventoryItem(CONST_SLOT_RING)); + sendInventoryItem(CONST_SLOT_AMMO, player->getInventoryItem(CONST_SLOT_AMMO)); + + sendStats(); + sendSkills(); + + //gameworld light-settings + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); + sendWorldLight(lightInfo); + + //player light level + sendCreatureLight(creature); + + //player vip + sendVIPEntries(); + + player->sendIcons(); +} + +void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport) +{ + if (creature == player) { + if (oldStackPos >= 10) { + sendMapDescription(newPos); + } else if (teleport) { + NetworkMessage msg; + RemoveTileThing(msg, oldPos, oldStackPos); + writeToOutputBuffer(msg); + sendMapDescription(newPos); + } else { + NetworkMessage msg; + if (oldPos.z == 7 && newPos.z >= 8) { + RemoveTileThing(msg, oldPos, oldStackPos); + } else { + msg.addByte(0x6D); + msg.addPosition(oldPos); + msg.addByte(oldStackPos); + msg.addPosition(newPos); + } + + if (newPos.z > oldPos.z) { + MoveDownCreature(msg, creature, newPos, oldPos); + } else if (newPos.z < oldPos.z) { + MoveUpCreature(msg, creature, newPos, oldPos); + } + + if (oldPos.y > newPos.y) { // north, for old x + msg.addByte(0x65); + GetMapDescription(oldPos.x - 8, newPos.y - 6, newPos.z, 18, 1, msg); + } else if (oldPos.y < newPos.y) { // south, for old x + msg.addByte(0x67); + GetMapDescription(oldPos.x - 8, newPos.y + 7, newPos.z, 18, 1, msg); + } + + if (oldPos.x < newPos.x) { // east, [with new y] + msg.addByte(0x66); + GetMapDescription(newPos.x + 9, newPos.y - 6, newPos.z, 1, 14, msg); + } else if (oldPos.x > newPos.x) { // west, [with new y] + msg.addByte(0x68); + GetMapDescription(newPos.x - 8, newPos.y - 6, newPos.z, 1, 14, msg); + } + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos) && canSee(creature->getPosition())) { + if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) { + sendRemoveTileThing(oldPos, oldStackPos); + sendAddCreature(creature, newPos, newStackPos, false); + } else { + NetworkMessage msg; + msg.addByte(0x6D); + msg.addPosition(oldPos); + msg.addByte(oldStackPos); + msg.addPosition(creature->getPosition()); + writeToOutputBuffer(msg); + } + } else if (canSee(oldPos)) { + sendRemoveTileThing(oldPos, oldStackPos); + } else if (canSee(creature->getPosition())) { + sendAddCreature(creature, newPos, newStackPos, false); + } +} + +void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) +{ + NetworkMessage msg; + if (item) { + msg.addByte(0x78); + msg.addByte(slot); + msg.addItem(item); + } else { + msg.addByte(0x79); + msg.addByte(slot); + } + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddContainerItem(uint8_t cid, const Item* item) +{ + NetworkMessage msg; + msg.addByte(0x70); + msg.addByte(cid); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item) +{ + NetworkMessage msg; + msg.addByte(0x71); + msg.addByte(cid); + msg.addByte(static_cast(slot)); + msg.addItem(item); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot) +{ + NetworkMessage msg; + msg.addByte(0x72); + msg.addByte(cid); + msg.addByte(static_cast(slot)); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite) +{ + NetworkMessage msg; + msg.addByte(0x96); + msg.add(windowTextId); + msg.addItem(item); + + if (canWrite) { + msg.add(maxlen); + msg.addString(item->getText()); + } else { + const std::string& text = item->getText(); + msg.add(text.size()); + msg.addString(text); + } + + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + msg.addString(writer); + } else { + msg.add(0x00); + } + + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + time_t writtenDate = item->getDate(); + if (writtenDate != 0) { + msg.addString(formatDateShort(writtenDate)); + } else { + msg.add(0x00); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x96); + msg.add(windowTextId); + msg.addItem(itemId, 1); + msg.add(text.size()); + msg.addString(text); + msg.add(0x00); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x97); + msg.addByte(0x00); + msg.add(windowTextId); + msg.addString(text); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendOutfitWindow() +{ + NetworkMessage msg; + msg.addByte(0xC8); + + Outfit_t currentOutfit = player->getDefaultOutfit(); + AddOutfit(msg, currentOutfit); + + if (player->getSex() == PLAYERSEX_MALE) { + msg.add(128); + if (player->isPremium()) { + msg.add(134); + } else { + msg.add(131); + } + } else { + msg.add(136); + if (player->isPremium()) { + msg.add(142); + } else { + msg.add(139); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) +{ + NetworkMessage msg; + msg.addByte(newStatus == VIPSTATUS_ONLINE ? 0xD3 : 0xD4); + msg.add(guid); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, VipStatus_t status) +{ + NetworkMessage msg; + msg.addByte(0xD2); + msg.add(guid); + msg.addString(name); + msg.addByte(status); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendVIPEntries() +{ + const std::forward_list& vipEntries = IOLoginData::getVIPEntries(player->getAccount()); + + for (const VIPEntry& entry : vipEntries) { + VipStatus_t vipStatus = VIPSTATUS_ONLINE; + + Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); + if (!vipPlayer || (vipPlayer->isInGhostMode() && !player->isAccessPlayer())) { + vipStatus = VIPSTATUS_OFFLINE; + } + + sendVIP(entry.guid, entry.name, vipStatus); + } +} + +////////////// Add common messages +void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) +{ + const Player* otherPlayer = creature->getPlayer(); + + if (known) { + msg.add(0x62); + msg.add(creature->getID()); + } else { + msg.add(0x61); + msg.add(remove); + msg.add(creature->getID()); + msg.addString(creature->getName()); + } + + msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); + msg.addByte(creature->getDirection()); + + if (!creature->isInGhostMode() && !creature->isInvisible()) { + AddOutfit(msg, creature->getCurrentOutfit()); + } else { + static Outfit_t outfit; + AddOutfit(msg, outfit); + } + + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); + msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); + msg.addByte(lightInfo.color); + + msg.add(creature->getStepSpeed()); + + msg.addByte(player->getSkullClient(creature)); + msg.addByte(player->getPartyShield(otherPlayer)); +} + +void ProtocolGame::AddPlayerStats(NetworkMessage& msg) +{ + msg.addByte(0xA0); + + msg.add(std::min(player->getHealth(), std::numeric_limits::max())); + msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); + msg.add(static_cast(player->getFreeCapacity() / 100.)); + if (player->getExperience() >= std::numeric_limits::max()) { + msg.add(0); + } else { + msg.add(static_cast(player->getExperience())); + } + msg.add(static_cast(player->getLevel())); + msg.addByte(player->getLevelPercent()); + msg.add(std::min(player->getMana(), std::numeric_limits::max())); + msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); + + msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); + msg.addByte(player->getMagicLevelPercent()); + + msg.addByte(player->getSoul()); +} + +void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) +{ + msg.addByte(0xA1); + + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + msg.addByte(std::min(player->getSkillLevel(i), std::numeric_limits::max())); + msg.addByte(player->getSkillPercent(i)); + } +} + +void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) +{ + msg.add(outfit.lookType); + + if (outfit.lookType != 0) { + msg.addByte(outfit.lookHead); + msg.addByte(outfit.lookBody); + msg.addByte(outfit.lookLegs); + msg.addByte(outfit.lookFeet); + } else { + msg.addItemId(outfit.lookTypeEx); + } +} + +void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo) +{ + msg.addByte(0x82); + msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.addByte(lightInfo.color); +} + +void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) +{ + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); + + msg.addByte(0x8D); + msg.add(creature->getID()); + msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); + msg.addByte(lightInfo.color); +} + +void ProtocolGame::AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId, const Position* pos /*= nullptr*/) +{ + msg.addByte(0xAA); + static uint32_t statementId = 0; + msg.add(++statementId); + + if (type != TALKTYPE_RVR_ANSWER) { + if (type != TALKTYPE_CHANNEL_R2) { + if (creature) { + msg.addString(creature->getName()); + } else { + msg.add(0); + } + } else { + msg.add(0); + } + } + + msg.addByte(type); + switch (type) { + case TALKTYPE_SAY: + case TALKTYPE_WHISPER: + case TALKTYPE_YELL: + case TALKTYPE_MONSTER_SAY: + case TALKTYPE_MONSTER_YELL: + if (!pos) { + msg.addPosition(creature->getPosition()); + } else { + msg.addPosition(*pos); + } + break; + case TALKTYPE_CHANNEL_Y: + case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: + case TALKTYPE_CHANNEL_O: + msg.add(channelId); + break; + case TALKTYPE_RVR_CHANNEL: + msg.add(0); + break; + default: + break; + } + + msg.addString(text); +} + +//tile +void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos) +{ + if (stackpos >= 10) { + return; + } + + msg.addByte(0x6C); + msg.addPosition(pos); + msg.addByte(stackpos); +} + +void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +{ + if (creature != player) { + return; + } + + //floor change up + msg.addByte(0xBE); + + //going to surface + if (newPos.z == 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 5, 18, 14, 3, skip); //(floor 7 and 6 already set) + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 4, 18, 14, 4, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 3, 18, 14, 5, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 2, 18, 14, 6, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 1, 18, 14, 7, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 0, 18, 14, 8, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + //underground, going one floor up (still underground) + else if (newPos.z > 7) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, oldPos.getZ() - 3, 18, 14, 3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + + //moving up a floor up makes us out of sync + //west + msg.addByte(0x68); + GetMapDescription(oldPos.x - 8, oldPos.y - 5, newPos.z, 1, 14, msg); + + //north + msg.addByte(0x65); + GetMapDescription(oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 1, msg); +} + +void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos) +{ + if (creature != player) { + return; + } + + //floor change down + msg.addByte(0xBF); + + //going from surface to underground + if (newPos.z == 8) { + int32_t skip = -1; + + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 14, -1, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 1, 18, 14, -2, skip); + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + //going further down + else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) { + int32_t skip = -1; + GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip); + + if (skip >= 0) { + msg.addByte(skip); + msg.addByte(0xFF); + } + } + + //moving down a floor makes us out of sync + //east + msg.addByte(0x66); + GetMapDescription(oldPos.x + 9, oldPos.y - 7, newPos.z, 1, 14, msg); + + //south + msg.addByte(0x67); + GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); +} + +void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) +{ + uint8_t opcode = msg.getByte(); + const std::string& buffer = msg.getString(); + + // process additional opcodes via lua script event + addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer); +} diff --git a/src/protocolgame.h b/src/protocolgame.h new file mode 100644 index 0000000..85ac0c2 --- /dev/null +++ b/src/protocolgame.h @@ -0,0 +1,271 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 +#define FS_PROTOCOLGAME_H_FACA2A2D1A9348B78E8FD7E8003EBB87 + +#include "protocol.h" +#include "chat.h" +#include "creature.h" +#include "tasks.h" + +class NetworkMessage; +class Player; +class Game; +class House; +class Container; +class Tile; +class Connection; +class Quest; +class ProtocolGame; +typedef std::shared_ptr ProtocolGame_ptr; + +extern Game g_game; + +struct TextMessage +{ + MessageClasses type; + std::string text; + TextMessage() = default; + TextMessage(MessageClasses type, std::string text) : type(type), text(std::move(text)) {} +}; + +class ProtocolGame final : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0x0A}; // Not required as we send first + + static const char* protocol_name() { + return "gameworld protocol"; + } + + explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} + + void login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem); + void logout(bool displayEffect, bool forced); + + uint16_t getVersion() const { + return version; + } + + private: + ProtocolGame_ptr getThis() { + return std::static_pointer_cast(shared_from_this()); + } + void connect(uint32_t playerId, OperatingSystem_t operatingSystem); + void sendUpdateRequest(); + void disconnectClient(const std::string& message) const; + void writeToOutputBuffer(const NetworkMessage& msg); + + void release() final; + + void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); + + bool canSee(int32_t x, int32_t y, int32_t z) const; + bool canSee(const Creature*) const; + bool canSee(const Position& pos) const; + + // we have all the parse methods + void parsePacket(NetworkMessage& msg) final; + void onRecvFirstMessage(NetworkMessage& msg) final; + + //Parse methods + void parseAutoWalk(NetworkMessage& msg); + void parseSetOutfit(NetworkMessage& msg); + void parseSay(NetworkMessage& msg); + void parseLookAt(NetworkMessage& msg); + void parseLookInBattleList(NetworkMessage& msg); + void parseFightModes(NetworkMessage& msg); + void parseAttack(NetworkMessage& msg); + void parseFollow(NetworkMessage& msg); + + void parseProcessRuleViolationReport(NetworkMessage& msg); + void parseCloseRuleViolationReport(NetworkMessage& msg); + + void parseBugReport(NetworkMessage& msg); + void parseDebugAssert(NetworkMessage& msg); + + void parseThrow(NetworkMessage& msg); + void parseUseItemEx(NetworkMessage& msg); + void parseUseWithCreature(NetworkMessage& msg); + void parseUseItem(NetworkMessage& msg); + void parseCloseContainer(NetworkMessage& msg); + void parseUpArrowContainer(NetworkMessage& msg); + void parseUpdateContainer(NetworkMessage& msg); + void parseTextWindow(NetworkMessage& msg); + void parseHouseWindow(NetworkMessage& msg); + + void parseInviteToParty(NetworkMessage& msg); + void parseJoinParty(NetworkMessage& msg); + void parseRevokePartyInvite(NetworkMessage& msg); + void parsePassPartyLeadership(NetworkMessage& msg); + + void parseSeekInContainer(NetworkMessage& msg); + + //trade methods + void parseRequestTrade(NetworkMessage& msg); + void parseLookInTrade(NetworkMessage& msg); + + //VIP methods + void parseAddVip(NetworkMessage& msg); + void parseRemoveVip(NetworkMessage& msg); + + void parseRotateItem(NetworkMessage& msg); + + //Channel tabs + void parseChannelInvite(NetworkMessage& msg); + void parseChannelExclude(NetworkMessage& msg); + void parseOpenChannel(NetworkMessage& msg); + void parseOpenPrivateChannel(NetworkMessage& msg); + void parseCloseChannel(NetworkMessage& msg); + + //Send functions + void sendClosePrivate(uint16_t channelId); + void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); + void sendChannelsDialog(); + void sendChannel(uint16_t channelId, const std::string& channelName); + void sendOpenPrivateChannel(const std::string& receiver); + void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); + void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); + void sendIcons(uint16_t icons); + + void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); + void sendMagicEffect(const Position& pos, uint8_t type); + void sendCreatureHealth(const Creature* creature); + void sendSkills(); + void sendPing(); + void sendPingBack(); + void sendCreatureTurn(const Creature* creature, uint32_t stackpos); + void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr); + + void sendCancelWalk(); + void sendChangeSpeed(const Creature* creature, uint32_t speed); + void sendCancelTarget(); + void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); + void sendStats(); + void sendTextMessage(const TextMessage& message); + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text); + + void sendCreatureShield(const Creature* creature); + void sendCreatureSkull(const Creature* creature); + + void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); + void sendCloseTrade(); + + void sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite); + void sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text); + void sendHouseWindow(uint32_t windowTextId, const std::string& text); + void sendOutfitWindow(); + + void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); + void sendVIP(uint32_t guid, const std::string& name, VipStatus_t status); + void sendVIPEntries(); + + void sendFightModes(); + + void sendCreatureLight(const Creature* creature); + void sendWorldLight(const LightInfo& lightInfo); + + void sendCreatureSquare(const Creature* creature, SquareColor_t color); + + //rule violations + void sendRemoveRuleViolationReport(const std::string& name); + void sendLockRuleViolation(); + void sendRuleViolationCancel(const std::string& name); + void sendRuleViolationsChannel(uint16_t channelId); + + //tiles + void sendMapDescription(const Position& pos); + + void sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos); + void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); + void sendRemoveTileThing(const Position& pos, uint32_t stackpos); + void sendUpdateTile(const Tile* tile, const Position& pos); + + void sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin); + void sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, + const Position& oldPos, int32_t oldStackPos, bool teleport); + + //containers + void sendAddContainerItem(uint8_t cid, const Item* item); + void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot); + + void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); + void sendCloseContainer(uint8_t cid); + + //inventory + void sendInventoryItem(slots_t slot, const Item* item); + + //Help functions + + // translate a tile to clientreadable format + void GetTileDescription(const Tile* tile, NetworkMessage& msg); + + // translate a floor to clientreadable format + void GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, int32_t offset, int32_t& skip); + + // translate a map area to clientreadable format + void GetMapDescription(int32_t x, int32_t y, int32_t z, + int32_t width, int32_t height, NetworkMessage& msg); + + void AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove); + void AddPlayerStats(NetworkMessage& msg); + void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); + void AddPlayerSkills(NetworkMessage& msg); + void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo); + void AddCreatureLight(NetworkMessage& msg, const Creature* creature); + void AddCreatureSpeak(NetworkMessage& msg, const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId, const Position* pos = nullptr); + + //tiles + static void RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos); + + void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + + //otclient + void parseExtendedOpcode(NetworkMessage& msg); + + friend class Player; + + // Helpers so we don't need to bind every time + template + void addGameTask(Callable function, Args&&... args) { + g_dispatcher.addTask(createTask(std::bind(function, &g_game, std::forward(args)...))); + } + + template + void addGameTaskTimed(uint32_t delay, Callable function, Args&&... args) { + g_dispatcher.addTask(createTask(delay, std::bind(function, &g_game, std::forward(args)...))); + } + + std::unordered_set knownCreatureSet; + Player* player = nullptr; + + uint32_t eventConnect = 0; + uint16_t version = CLIENT_VERSION_MIN; + + bool debugAssertSent = false; + bool acceptPackets = false; +}; + +#endif diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp new file mode 100644 index 0000000..a12f107 --- /dev/null +++ b/src/protocollogin.cpp @@ -0,0 +1,181 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocollogin.h" + +#include "outputmessage.h" +#include "tasks.h" + +#include "configmanager.h" +#include "iologindata.h" +#include "ban.h" +#include "game.h" + +extern ConfigManager g_config; +extern Game g_game; + +void ProtocolLogin::sendUpdateRequest() +{ + auto output = OutputMessagePool::getOutputMessage(); + + output->addByte(0x1E); + send(output); + + disconnect(); +} + +void ProtocolLogin::disconnectClient(const std::string& message) +{ + auto output = OutputMessagePool::getOutputMessage(); + + output->addByte(0x0A); + output->addString(message); + send(output); + + disconnect(); +} + +void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password) +{ + Account account; + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Accountnumber or password is not correct."); + return; + } + + auto output = OutputMessagePool::getOutputMessage(); + + //Update premium days + Game::updatePremium(account); + + const std::string& motd = g_config.getString(ConfigManager::MOTD); + if (!motd.empty()) { + //Add MOTD + output->addByte(0x14); + + std::ostringstream ss; + ss << g_game.getMotdNum() << "\n" << motd; + output->addString(ss.str()); + } + + //Add char list + output->addByte(0x64); + + uint8_t size = std::min(std::numeric_limits::max(), account.characters.size()); + output->addByte(size); + for (uint8_t i = 0; i < size; i++) { + output->addString(account.characters[i]); + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->add(inet_addr(g_config.getString(ConfigManager::IP).c_str())); + output->add(g_config.getNumber(ConfigManager::GAME_PORT)); + } + + //Add premium days + if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { + output->add(0xFFFF); + } else { + output->add(account.premiumDays); + } + + send(output); + + disconnect(); +} + +void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + disconnect(); + return; + } + + msg.skipBytes(2); // client OS + + /*uint16_t version =*/ msg.get(); + msg.skipBytes(12); + + /* + * Skipped bytes: + * 4 bytes: protocolVersion + * 12 bytes: dat, spr, pic signatures (4 bytes each) + */ + + if (!Protocol::RSA_decrypt(msg)) { + disconnect(); + return; + } + + uint32_t key[4]; + key[0] = msg.get(); + key[1] = msg.get(); + key[2] = msg.get(); + key[3] = msg.get(); + enableXTEAEncryption(); + setXTEAKey(key); + + /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + //sendUpdateRequest(); + disconnectClient("Use Tibia 7.72 to login!"); + return; + }*/ + + if (g_game.getGameState() == GAME_STATE_STARTUP) { + disconnectClient("Gameworld is starting up. Please wait."); + return; + } + + if (g_game.getGameState() == GAME_STATE_MAINTAIN) { + disconnectClient("Gameworld is under maintenance.\nPlease re-connect in a while."); + return; + } + + BanInfo banInfo; + auto connection = getConnection(); + if (!connection) { + return; + } + + if (IOBan::isIpBanned(connection->getIP(), banInfo)) { + if (banInfo.reason.empty()) { + banInfo.reason = "(none)"; + } + + std::ostringstream ss; + ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason; + disconnectClient(ss.str()); + return; + } + + uint32_t accountNumber = msg.get(); + if (!accountNumber) { + disconnectClient("Invalid account number."); + return; + } + + std::string password = msg.getString(); + if (password.empty()) { + disconnectClient("Invalid password."); + return; + } + + auto thisPtr = std::static_pointer_cast(shared_from_this()); + g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password))); +} diff --git a/src/protocollogin.h b/src/protocollogin.h new file mode 100644 index 0000000..4a31331 --- /dev/null +++ b/src/protocollogin.h @@ -0,0 +1,50 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D +#define FS_PROTOCOLLOGIN_H_1238F4B473074DF2ABC595C29E81C46D + +#include "protocol.h" + +class NetworkMessage; +class OutputMessage; + +class ProtocolLogin : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0x01}; + enum {use_checksum = true}; + static const char* protocol_name() { + return "login protocol"; + } + + explicit ProtocolLogin(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg); + + protected: + void sendUpdateRequest(); + void disconnectClient(const std::string& message); + + void getCharacterList(uint32_t accountNumber, const std::string& password); +}; + +#endif diff --git a/src/protocolstatus.cpp b/src/protocolstatus.cpp new file mode 100644 index 0000000..bd6057d --- /dev/null +++ b/src/protocolstatus.cpp @@ -0,0 +1,247 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocolstatus.h" +#include "configmanager.h" +#include "game.h" +#include "outputmessage.h" + +extern ConfigManager g_config; +extern Game g_game; + +std::map ProtocolStatus::ipConnectMap; +const uint64_t ProtocolStatus::start = OTSYS_TIME(); + +enum RequestedInfo_t : uint16_t { + REQUEST_BASIC_SERVER_INFO = 1 << 0, + REQUEST_OWNER_SERVER_INFO = 1 << 1, + REQUEST_MISC_SERVER_INFO = 1 << 2, + REQUEST_PLAYERS_INFO = 1 << 3, + REQUEST_MAP_INFO = 1 << 4, + REQUEST_EXT_PLAYERS_INFO = 1 << 5, + REQUEST_PLAYER_STATUS_INFO = 1 << 6, + REQUEST_SERVER_SOFTWARE_INFO = 1 << 7, +}; + +void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg) +{ + uint32_t ip = getIP(); + if (ip != 0x0100007F) { + std::string ipStr = convertIPToString(ip); + if (ipStr != g_config.getString(ConfigManager::IP)) { + std::map::const_iterator it = ipConnectMap.find(ip); + if (it != ipConnectMap.end() && (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) { + disconnect(); + return; + } + } + } + + ipConnectMap[ip] = OTSYS_TIME(); + + switch (msg.getByte()) { + //XML info protocol + case 0xFF: { + if (msg.getString(4) == "info") { + g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendStatusString, + std::static_pointer_cast(shared_from_this())))); + return; + } + break; + } + + //Another ServerInfo protocol + case 0x01: { + uint16_t requestedInfo = msg.get(); // only a Byte is necessary, though we could add new info here + std::string characterName; + if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { + characterName = msg.getString(); + } + g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast(shared_from_this()), + requestedInfo, characterName))); + return; + } + + default: + break; + } + disconnect(); +} + +void ProtocolStatus::sendStatusString() +{ + auto output = OutputMessagePool::getOutputMessage(); + + setRawMessages(true); + + pugi::xml_document doc; + + pugi::xml_node decl = doc.prepend_child(pugi::node_declaration); + decl.append_attribute("version") = "1.0"; + + pugi::xml_node tsqp = doc.append_child("tsqp"); + tsqp.append_attribute("version") = "1.0"; + + pugi::xml_node serverinfo = tsqp.append_child("serverinfo"); + uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000; + serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str(); + serverinfo.append_attribute("ip") = g_config.getString(ConfigManager::IP).c_str(); + serverinfo.append_attribute("servername") = g_config.getString(ConfigManager::SERVER_NAME).c_str(); + serverinfo.append_attribute("port") = std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)).c_str(); + serverinfo.append_attribute("location") = g_config.getString(ConfigManager::LOCATION).c_str(); + serverinfo.append_attribute("url") = g_config.getString(ConfigManager::URL).c_str(); + serverinfo.append_attribute("server") = STATUS_SERVER_NAME; + serverinfo.append_attribute("version") = STATUS_SERVER_VERSION; + serverinfo.append_attribute("client") = CLIENT_VERSION_STR; + + pugi::xml_node owner = tsqp.append_child("owner"); + owner.append_attribute("name") = g_config.getString(ConfigManager::OWNER_NAME).c_str(); + owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str(); + + pugi::xml_node players = tsqp.append_child("players"); + uint32_t real = 0; + + std::map listIP; + + for (const auto& it : g_game.getPlayers()) { + if (it.second->getIP() != 0) { + auto ip = listIP.find(it.second->getIP()); + if (ip != listIP.end()) { + listIP[it.second->getIP()]++; + if (listIP[it.second->getIP()] < 5) { + real++; + } + } else { + listIP[it.second->getIP()] = 1; + real++; + } + } + } + players.append_attribute("online") = std::to_string(real).c_str(); + + players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str(); + players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str(); + + pugi::xml_node monsters = tsqp.append_child("monsters"); + monsters.append_attribute("total") = std::to_string(g_game.getMonstersOnline()).c_str(); + + pugi::xml_node npcs = tsqp.append_child("npcs"); + npcs.append_attribute("total") = std::to_string(g_game.getNpcsOnline()).c_str(); + + pugi::xml_node rates = tsqp.append_child("rates"); + rates.append_attribute("experience") = std::to_string(g_config.getNumber(ConfigManager::RATE_EXPERIENCE)).c_str(); + rates.append_attribute("skill") = std::to_string(g_config.getNumber(ConfigManager::RATE_SKILL)).c_str(); + rates.append_attribute("loot") = std::to_string(g_config.getNumber(ConfigManager::RATE_LOOT)).c_str(); + rates.append_attribute("magic") = std::to_string(g_config.getNumber(ConfigManager::RATE_MAGIC)).c_str(); + rates.append_attribute("spawn") = std::to_string(g_config.getNumber(ConfigManager::RATE_SPAWN)).c_str(); + + pugi::xml_node map = tsqp.append_child("map"); + map.append_attribute("name") = g_config.getString(ConfigManager::MAP_NAME).c_str(); + map.append_attribute("author") = g_config.getString(ConfigManager::MAP_AUTHOR).c_str(); + + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + map.append_attribute("width") = std::to_string(mapWidth).c_str(); + map.append_attribute("height") = std::to_string(mapHeight).c_str(); + + pugi::xml_node motd = tsqp.append_child("motd"); + motd.text() = g_config.getString(ConfigManager::MOTD).c_str(); + + std::ostringstream ss; + doc.save(ss, "", pugi::format_raw); + + std::string data = ss.str(); + output->addBytes(data.c_str(), data.size()); + send(output); + disconnect(); +} + +void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& characterName) +{ + auto output = OutputMessagePool::getOutputMessage(); + + if (requestedInfo & REQUEST_BASIC_SERVER_INFO) { + output->addByte(0x10); + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->addString(g_config.getString(ConfigManager::IP)); + output->addString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT))); + } + + if (requestedInfo & REQUEST_OWNER_SERVER_INFO) { + output->addByte(0x11); + output->addString(g_config.getString(ConfigManager::OWNER_NAME)); + output->addString(g_config.getString(ConfigManager::OWNER_EMAIL)); + } + + if (requestedInfo & REQUEST_MISC_SERVER_INFO) { + output->addByte(0x12); + output->addString(g_config.getString(ConfigManager::MOTD)); + output->addString(g_config.getString(ConfigManager::LOCATION)); + output->addString(g_config.getString(ConfigManager::URL)); + output->add((OTSYS_TIME() - ProtocolStatus::start) / 1000); + } + + if (requestedInfo & REQUEST_PLAYERS_INFO) { + output->addByte(0x20); + output->add(g_game.getPlayersOnline()); + output->add(g_config.getNumber(ConfigManager::MAX_PLAYERS)); + output->add(g_game.getPlayersRecord()); + } + + if (requestedInfo & REQUEST_MAP_INFO) { + output->addByte(0x30); + output->addString(g_config.getString(ConfigManager::MAP_NAME)); + output->addString(g_config.getString(ConfigManager::MAP_AUTHOR)); + uint32_t mapWidth, mapHeight; + g_game.getMapDimensions(mapWidth, mapHeight); + output->add(mapWidth); + output->add(mapHeight); + } + + if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) { + output->addByte(0x21); // players info - online players list + + const auto& players = g_game.getPlayers(); + output->add(players.size()); + for (const auto& it : players) { + output->addString(it.second->getName()); + output->add(it.second->getLevel()); + } + } + + if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { + output->addByte(0x22); // players info - online status info of a player + if (g_game.getPlayerByName(characterName) != nullptr) { + output->addByte(0x01); + } else { + output->addByte(0x00); + } + } + + if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) { + output->addByte(0x23); // server software info + output->addString(STATUS_SERVER_NAME); + output->addString(STATUS_SERVER_VERSION); + output->addString(CLIENT_VERSION_STR); + } + send(output); + disconnect(); +} diff --git a/src/protocolstatus.h b/src/protocolstatus.h new file mode 100644 index 0000000..a7e499d --- /dev/null +++ b/src/protocolstatus.h @@ -0,0 +1,50 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 +#define FS_STATUS_H_8B28B354D65B4C0483E37AD1CA316EB4 + +#include "networkmessage.h" +#include "protocol.h" + +class ProtocolStatus final : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0xFF}; + enum {use_checksum = false}; + static const char* protocol_name() { + return "status protocol"; + } + + explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) final; + + void sendStatusString(); + void sendInfo(uint16_t requestedInfo, const std::string& characterName); + + static const uint64_t start; + + protected: + static std::map ipConnectMap; +}; + +#endif diff --git a/src/pugicast.h b/src/pugicast.h new file mode 100644 index 0000000..b13158c --- /dev/null +++ b/src/pugicast.h @@ -0,0 +1,39 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 +#define FS_PUGICAST_H_07810DF7954D411EB14A16C3ED2A7548 + +#include + +namespace pugi { + template + T cast(const pugi::char_t* str) + { + T value; + try { + value = boost::lexical_cast(str); + } catch (boost::bad_lexical_cast&) { + value = T(); + } + return value; + } +} + +#endif diff --git a/src/raids.cpp b/src/raids.cpp new file mode 100644 index 0000000..b54d8d1 --- /dev/null +++ b/src/raids.cpp @@ -0,0 +1,595 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "raids.h" + +#include "pugicast.h" + +#include "game.h" +#include "configmanager.h" +#include "scheduler.h" +#include "monster.h" + +extern Game g_game; +extern ConfigManager g_config; + +Raids::Raids() +{ + scriptInterface.initState(); +} + +Raids::~Raids() +{ + for (Raid* raid : raidList) { + delete raid; + } +} + +bool Raids::loadFromXml() +{ + if (isLoaded()) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/raids/raids.xml"); + if (!result) { + printXMLError("Error - Raids::loadFromXml", "data/raids/raids.xml", result); + return false; + } + + for (auto raidNode : doc.child("raids").children()) { + std::string name, file; + uint32_t interval, margin; + + pugi::xml_attribute attr; + if ((attr = raidNode.attribute("name"))) { + name = attr.as_string(); + } else { + std::cout << "[Error - Raids::loadFromXml] Name tag missing for raid" << std::endl; + continue; + } + + if ((attr = raidNode.attribute("file"))) { + file = attr.as_string(); + } else { + std::ostringstream ss; + ss << "raids/" << name << ".xml"; + file = ss.str(); + std::cout << "[Warning - Raids::loadFromXml] File tag missing for raid " << name << ". Using default: " << file << std::endl; + } + + interval = pugi::cast(raidNode.attribute("interval2").value()) * 60; + if (interval == 0) { + std::cout << "[Error - Raids::loadFromXml] interval2 tag missing or zero (would divide by 0) for raid: " << name << std::endl; + continue; + } + + if ((attr = raidNode.attribute("margin"))) { + margin = pugi::cast(attr.value()) * 60 * 1000; + } else { + std::cout << "[Warning - Raids::loadFromXml] margin tag missing for raid: " << name << std::endl; + margin = 0; + } + + bool repeat; + if ((attr = raidNode.attribute("repeat"))) { + repeat = booleanString(attr.as_string()); + } else { + repeat = false; + } + + Raid* newRaid = new Raid(name, interval, margin, repeat); + if (newRaid->loadFromXml("data/raids/" + file)) { + raidList.push_back(newRaid); + } else { + std::cout << "[Error - Raids::loadFromXml] Failed to load raid: " << name << std::endl; + delete newRaid; + } + } + + loaded = true; + return true; +} + +static constexpr int32_t MAX_RAND_RANGE = 10000000; + +bool Raids::startup() +{ + if (!isLoaded() || isStarted()) { + return false; + } + + setLastRaidEnd(OTSYS_TIME()); + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); + + started = true; + return started; +} + +void Raids::checkRaids() +{ + if (!getRunning()) { + uint64_t now = OTSYS_TIME(); + + for (auto it = raidList.begin(), end = raidList.end(); it != end; ++it) { + Raid* raid = *it; + if (now >= (getLastRaidEnd() + raid->getMargin())) { + if (((MAX_RAND_RANGE * CHECK_RAIDS_INTERVAL) / raid->getInterval()) >= static_cast(uniform_random(0, MAX_RAND_RANGE))) { + setRunning(raid); + raid->startRaid(); + + if (!raid->canBeRepeated()) { + raidList.erase(it); + } + break; + } + } + } + } + + checkRaidsEvent = g_scheduler.addEvent(createSchedulerTask(CHECK_RAIDS_INTERVAL * 1000, std::bind(&Raids::checkRaids, this))); +} + +void Raids::clear() +{ + g_scheduler.stopEvent(checkRaidsEvent); + checkRaidsEvent = 0; + + for (Raid* raid : raidList) { + raid->stopEvents(); + delete raid; + } + raidList.clear(); + + loaded = false; + started = false; + running = nullptr; + lastRaidEnd = 0; + + scriptInterface.reInitState(); +} + +bool Raids::reload() +{ + clear(); + return loadFromXml(); +} + +Raid* Raids::getRaidByName(const std::string& name) +{ + for (Raid* raid : raidList) { + if (strcasecmp(raid->getName().c_str(), name.c_str()) == 0) { + return raid; + } + } + return nullptr; +} + +Raid::~Raid() +{ + for (RaidEvent* raidEvent : raidEvents) { + delete raidEvent; + } +} + +bool Raid::loadFromXml(const std::string& filename) +{ + if (isLoaded()) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Raid::loadFromXml", filename, result); + return false; + } + + for (auto eventNode : doc.child("raid").children()) { + RaidEvent* event; + if (strcasecmp(eventNode.name(), "announce") == 0) { + event = new AnnounceEvent(); + } else if (strcasecmp(eventNode.name(), "singlespawn") == 0) { + event = new SingleSpawnEvent(); + } else if (strcasecmp(eventNode.name(), "areaspawn") == 0) { + event = new AreaSpawnEvent(); + } else if (strcasecmp(eventNode.name(), "script") == 0) { + event = new ScriptEvent(&g_game.raids.getScriptInterface()); + } else { + continue; + } + + if (event->configureRaidEvent(eventNode)) { + raidEvents.push_back(event); + } else { + std::cout << "[Error - Raid::loadFromXml] In file (" << filename << "), eventNode: " << eventNode.name() << std::endl; + delete event; + } + } + + //sort by delay time + std::sort(raidEvents.begin(), raidEvents.end(), RaidEvent::compareEvents); + + loaded = true; + return true; +} + +void Raid::startRaid() +{ + RaidEvent* raidEvent = getNextRaidEvent(); + if (raidEvent) { + state = RAIDSTATE_EXECUTING; + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(raidEvent->getDelay(), std::bind(&Raid::executeRaidEvent, this, raidEvent))); + } +} + +void Raid::executeRaidEvent(RaidEvent* raidEvent) +{ + if (raidEvent->executeEvent()) { + nextEvent++; + RaidEvent* newRaidEvent = getNextRaidEvent(); + + if (newRaidEvent) { + uint32_t ticks = static_cast(std::max(RAID_MINTICKS, newRaidEvent->getDelay() - raidEvent->getDelay())); + nextEventEvent = g_scheduler.addEvent(createSchedulerTask(ticks, std::bind(&Raid::executeRaidEvent, this, newRaidEvent))); + } else { + resetRaid(); + } + } else { + resetRaid(); + } +} + +void Raid::resetRaid() +{ + nextEvent = 0; + state = RAIDSTATE_IDLE; + g_game.raids.setRunning(nullptr); + g_game.raids.setLastRaidEnd(OTSYS_TIME()); +} + +void Raid::stopEvents() +{ + if (nextEventEvent != 0) { + g_scheduler.stopEvent(nextEventEvent); + nextEventEvent = 0; + } +} + +RaidEvent* Raid::getNextRaidEvent() +{ + if (nextEvent < raidEvents.size()) { + return raidEvents[nextEvent]; + } else { + return nullptr; + } +} + +bool RaidEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + pugi::xml_attribute delayAttribute = eventNode.attribute("delay"); + if (!delayAttribute) { + std::cout << "[Error] Raid: delay tag missing." << std::endl; + return false; + } + + delay = std::max(RAID_MINTICKS, pugi::cast(delayAttribute.value())); + return true; +} + +bool AnnounceEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute messageAttribute = eventNode.attribute("message"); + if (!messageAttribute) { + std::cout << "[Error] Raid: message tag missing for announce event." << std::endl; + return false; + } + message = messageAttribute.as_string(); + + pugi::xml_attribute typeAttribute = eventNode.attribute("type"); + if (typeAttribute) { + std::string tmpStrValue = asLowerCaseString(typeAttribute.as_string()); + if (tmpStrValue == "warning") { + messageType = MESSAGE_STATUS_WARNING; + } else if (tmpStrValue == "event") { + messageType = MESSAGE_EVENT_ADVANCE; + } else if (tmpStrValue == "default") { + messageType = MESSAGE_EVENT_DEFAULT; + } else if (tmpStrValue == "description") { + messageType = MESSAGE_INFO_DESCR; + } else if (tmpStrValue == "smallstatus") { + messageType = MESSAGE_STATUS_SMALL; + } else if (tmpStrValue == "blueconsole") { + messageType = MESSAGE_STATUS_CONSOLE_BLUE; + } else if (tmpStrValue == "redconsole") { + messageType = MESSAGE_STATUS_CONSOLE_RED; + } else { + std::cout << "[Notice] Raid: Unknown type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + } + } else { + messageType = MESSAGE_EVENT_ADVANCE; + std::cout << "[Notice] Raid: type tag missing for announce event. Using default: " << static_cast(messageType) << std::endl; + } + return true; +} + +bool AnnounceEvent::executeEvent() +{ + g_game.broadcastMessage(message, messageType); + return true; +} + +bool SingleSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = eventNode.attribute("name"))) { + monsterName = attr.as_string(); + } else { + std::cout << "[Error] Raid: name tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("x"))) { + position.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: x tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("y"))) { + position.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: y tag missing for singlespawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("z"))) { + position.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: z tag missing for singlespawn event." << std::endl; + return false; + } + return true; +} + +bool SingleSpawnEvent::executeEvent() +{ + Monster* monster = Monster::createMonster(monsterName); + if (!monster) { + std::cout << "[Error] Raids: Cant create monster " << monsterName << std::endl; + return false; + } + + if (!g_game.placeCreature(monster, position, false, true)) { + delete monster; + std::cout << "[Error] Raids: Cant place monster " << monsterName << std::endl; + return false; + } + return true; +} + +bool AreaSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = eventNode.attribute("radius"))) { + int32_t radius = pugi::cast(attr.value()); + Position centerPos; + + if ((attr = eventNode.attribute("centerx"))) { + centerPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centerx tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("centery"))) { + centerPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centery tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("centerz"))) { + centerPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: centerz tag missing for areaspawn event." << std::endl; + return false; + } + + fromPos.x = std::max(0, centerPos.getX() - radius); + fromPos.y = std::max(0, centerPos.getY() - radius); + fromPos.z = centerPos.z; + + toPos.x = std::min(0xFFFF, centerPos.getX() + radius); + toPos.y = std::min(0xFFFF, centerPos.getY() + radius); + toPos.z = centerPos.z; + } else { + if ((attr = eventNode.attribute("fromx"))) { + fromPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromx tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("fromy"))) { + fromPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromy tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("fromz"))) { + fromPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: fromz tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("tox"))) { + toPos.x = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: tox tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("toy"))) { + toPos.y = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: toy tag missing for areaspawn event." << std::endl; + return false; + } + + if ((attr = eventNode.attribute("toz"))) { + toPos.z = pugi::cast(attr.value()); + } else { + std::cout << "[Error] Raid: toz tag missing for areaspawn event." << std::endl; + return false; + } + } + + if ((attr = eventNode.attribute("lifetime"))) { + lifetime = pugi::cast(attr.value()); + } + + for (auto monsterNode : eventNode.children()) { + const char* name; + + if ((attr = monsterNode.attribute("name"))) { + name = attr.value(); + } else { + std::cout << "[Error] Raid: name tag missing for monster node." << std::endl; + return false; + } + + uint32_t minAmount; + if ((attr = monsterNode.attribute("minamount"))) { + minAmount = pugi::cast(attr.value()); + } else { + minAmount = 0; + } + + uint32_t maxAmount; + if ((attr = monsterNode.attribute("maxamount"))) { + maxAmount = pugi::cast(attr.value()); + } else { + maxAmount = 0; + } + + if (maxAmount == 0 && minAmount == 0) { + if ((attr = monsterNode.attribute("amount"))) { + minAmount = pugi::cast(attr.value()); + maxAmount = minAmount; + } else { + std::cout << "[Error] Raid: amount tag missing for monster node." << std::endl; + return false; + } + } + + spawnList.emplace_back(name, minAmount, maxAmount); + } + return true; +} + +bool AreaSpawnEvent::executeEvent() +{ + for (const MonsterSpawn& spawn : spawnList) { + uint32_t amount = uniform_random(spawn.minAmount, spawn.maxAmount); + for (uint32_t i = 0; i < amount; ++i) { + Monster* monster = Monster::createMonster(spawn.name); + if (!monster) { + std::cout << "[Error - AreaSpawnEvent::executeEvent] Can't create monster " << spawn.name << std::endl; + return false; + } + + bool success = false; + for (int32_t tries = 0; tries < MAXIMUM_TRIES_PER_MONSTER; tries++) { + Tile* tile = g_game.map.getTile(uniform_random(fromPos.x, toPos.x), uniform_random(fromPos.y, toPos.y), uniform_random(fromPos.z, toPos.z)); + if (tile && !tile->isMoveableBlocking() && !tile->hasFlag(TILESTATE_PROTECTIONZONE) && tile->getTopCreature() == nullptr && g_game.placeCreature(monster, tile->getPosition(), false, true)) { + success = true; + break; + } + } + + if (!success) { + delete monster; + } + + if (lifetime > 0) { + monster->lifetime = OTSYS_TIME() + lifetime; + } + } + } + return true; +} + +bool ScriptEvent::configureRaidEvent(const pugi::xml_node& eventNode) +{ + if (!RaidEvent::configureRaidEvent(eventNode)) { + return false; + } + + pugi::xml_attribute scriptAttribute = eventNode.attribute("script"); + if (!scriptAttribute) { + std::cout << "Error: [ScriptEvent::configureRaidEvent] No script file found for raid" << std::endl; + return false; + } + + if (!loadScript("data/raids/scripts/" + std::string(scriptAttribute.as_string()))) { + std::cout << "Error: [ScriptEvent::configureRaidEvent] Can not load raid script." << std::endl; + return false; + } + return true; +} + +std::string ScriptEvent::getScriptEventName() const +{ + return "onRaid"; +} + +bool ScriptEvent::executeEvent() +{ + //onRaid() + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - ScriptEvent::onRaid] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + scriptInterface->pushFunction(scriptId); + + return scriptInterface->callFunction(0); +} diff --git a/src/raids.h b/src/raids.h new file mode 100644 index 0000000..2239107 --- /dev/null +++ b/src/raids.h @@ -0,0 +1,233 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 +#define FS_RAIDS_H_3583C7C054584881856D55765DEDAFA9 + +#include "const.h" +#include "position.h" +#include "baseevents.h" + +enum RaidState_t { + RAIDSTATE_IDLE, + RAIDSTATE_EXECUTING, +}; + +struct MonsterSpawn { + MonsterSpawn(std::string name, uint32_t minAmount, uint32_t maxAmount) : + name(std::move(name)), minAmount(minAmount), maxAmount(maxAmount) {} + + std::string name; + uint32_t minAmount; + uint32_t maxAmount; +}; + +//How many times it will try to find a tile to add the monster to before giving up +static constexpr int32_t MAXIMUM_TRIES_PER_MONSTER = 10; +static constexpr int32_t CHECK_RAIDS_INTERVAL = 60; +static constexpr int32_t RAID_MINTICKS = 1000; + +class Raid; +class RaidEvent; + +class Raids +{ + public: + Raids(); + ~Raids(); + + // non-copyable + Raids(const Raids&) = delete; + Raids& operator=(const Raids&) = delete; + + bool loadFromXml(); + bool startup(); + + void clear(); + bool reload(); + + bool isLoaded() const { + return loaded; + } + bool isStarted() const { + return started; + } + + Raid* getRunning() { + return running; + } + void setRunning(Raid* newRunning) { + running = newRunning; + } + + Raid* getRaidByName(const std::string& name); + + uint64_t getLastRaidEnd() const { + return lastRaidEnd; + } + void setLastRaidEnd(uint64_t newLastRaidEnd) { + lastRaidEnd = newLastRaidEnd; + } + + void checkRaids(); + + LuaScriptInterface& getScriptInterface() { + return scriptInterface; + } + + private: + LuaScriptInterface scriptInterface{"Raid Interface"}; + + std::list raidList; + Raid* running = nullptr; + uint64_t lastRaidEnd = 0; + uint32_t checkRaidsEvent = 0; + bool loaded = false; + bool started = false; +}; + +class Raid +{ + public: + Raid(std::string name, uint32_t interval, uint32_t marginTime, bool repeat) : + name(std::move(name)), interval(interval), margin(marginTime), repeat(repeat) {} + ~Raid(); + + // non-copyable + Raid(const Raid&) = delete; + Raid& operator=(const Raid&) = delete; + + bool loadFromXml(const std::string& filename); + + void startRaid(); + + void executeRaidEvent(RaidEvent* raidEvent); + void resetRaid(); + + RaidEvent* getNextRaidEvent(); + void setState(RaidState_t newState) { + state = newState; + } + const std::string& getName() const { + return name; + } + + bool isLoaded() const { + return loaded; + } + uint64_t getMargin() const { + return margin; + } + uint32_t getInterval() const { + return interval; + } + bool canBeRepeated() const { + return repeat; + } + + void stopEvents(); + + private: + std::vector raidEvents; + std::string name; + uint32_t interval; + uint32_t nextEvent = 0; + uint64_t margin; + RaidState_t state = RAIDSTATE_IDLE; + uint32_t nextEventEvent = 0; + bool loaded = false; + bool repeat; +}; + +class RaidEvent +{ + public: + virtual ~RaidEvent() = default; + + virtual bool configureRaidEvent(const pugi::xml_node& eventNode); + + virtual bool executeEvent() = 0; + uint32_t getDelay() const { + return delay; + } + + static bool compareEvents(const RaidEvent* lhs, const RaidEvent* rhs) { + return lhs->getDelay() < rhs->getDelay(); + } + + private: + uint32_t delay; +}; + +class AnnounceEvent final : public RaidEvent +{ + public: + AnnounceEvent() = default; + + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::string message; + MessageClasses messageType = MESSAGE_EVENT_ADVANCE; +}; + +class SingleSpawnEvent final : public RaidEvent +{ + public: + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::string monsterName; + Position position; +}; + +class AreaSpawnEvent final : public RaidEvent +{ + public: + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + + bool executeEvent() final; + + private: + std::list spawnList; + Position fromPos, toPos; + uint32_t lifetime = 0; +}; + +class ScriptEvent final : public RaidEvent, public Event +{ + public: + explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} + + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureEvent(const pugi::xml_node&) final { + return false; + } + + bool executeEvent() final; + + protected: + std::string getScriptEventName() const final; +}; + +#endif diff --git a/src/rsa.cpp b/src/rsa.cpp new file mode 100644 index 0000000..86c425b --- /dev/null +++ b/src/rsa.cpp @@ -0,0 +1,92 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "rsa.h" + +RSA::RSA() +{ + mpz_init(n); + mpz_init2(d, 1024); +} + +RSA::~RSA() +{ + mpz_clear(n); + mpz_clear(d); +} + +void RSA::setKey(const char* pString, const char* qString) +{ + mpz_t p, q, e; + mpz_init2(p, 1024); + mpz_init2(q, 1024); + mpz_init(e); + + mpz_set_str(p, pString, 10); + mpz_set_str(q, qString, 10); + + // e = 65537 + mpz_set_ui(e, 65537); + + // n = p * q + mpz_mul(n, p, q); + + mpz_t p_1, q_1, pq_1; + mpz_init2(p_1, 1024); + mpz_init2(q_1, 1024); + mpz_init2(pq_1, 1024); + + mpz_sub_ui(p_1, p, 1); + mpz_sub_ui(q_1, q, 1); + + // pq_1 = (p -1)(q - 1) + mpz_mul(pq_1, p_1, q_1); + + // d = e^-1 mod (p - 1)(q - 1) + mpz_invert(d, e, pq_1); + + mpz_clear(p_1); + mpz_clear(q_1); + mpz_clear(pq_1); + + mpz_clear(p); + mpz_clear(q); + mpz_clear(e); +} + +void RSA::decrypt(char* msg) const +{ + mpz_t c, m; + mpz_init2(c, 1024); + mpz_init2(m, 1024); + + mpz_import(c, 128, 1, 1, 0, 0, msg); + + // m = c^d mod n + mpz_powm(m, c, d, n); + + size_t count = (mpz_sizeinbase(m, 2) + 7) / 8; + memset(msg, 0, 128 - count); + mpz_export(msg + (128 - count), nullptr, 1, 1, 0, 0, m); + + mpz_clear(c); + mpz_clear(m); +} diff --git a/src/rsa.h b/src/rsa.h new file mode 100644 index 0000000..74a9850 --- /dev/null +++ b/src/rsa.h @@ -0,0 +1,43 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 +#define FS_RSA_H_C4E277DA8E884B578DDBF0566F504E91 + +#include + +class RSA +{ + public: + RSA(); + ~RSA(); + + // non-copyable + RSA(const RSA&) = delete; + RSA& operator=(const RSA&) = delete; + + void setKey(const char* pString, const char* qString); + void decrypt(char* msg) const; + + private: + //use only GMP + mpz_t n, d; +}; + +#endif diff --git a/src/scheduler.cpp b/src/scheduler.cpp new file mode 100644 index 0000000..d5daf69 --- /dev/null +++ b/src/scheduler.cpp @@ -0,0 +1,134 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "scheduler.h" + +void Scheduler::threadMain() +{ + std::unique_lock eventLockUnique(eventLock, std::defer_lock); + while (getState() != THREAD_STATE_TERMINATED) { + std::cv_status ret = std::cv_status::no_timeout; + + eventLockUnique.lock(); + if (eventList.empty()) { + eventSignal.wait(eventLockUnique); + } else { + ret = eventSignal.wait_until(eventLockUnique, eventList.top()->getCycle()); + } + + // the mutex is locked again now... + if (ret == std::cv_status::timeout) { + // ok we had a timeout, so there has to be an event we have to execute... + SchedulerTask* task = eventList.top(); + eventList.pop(); + + // check if the event was stopped + auto it = eventIds.find(task->getEventId()); + if (it == eventIds.end()) { + eventLockUnique.unlock(); + delete task; + continue; + } + eventIds.erase(it); + eventLockUnique.unlock(); + + task->setDontExpire(); + g_dispatcher.addTask(task, true); + } else { + eventLockUnique.unlock(); + } + } +} + +uint32_t Scheduler::addEvent(SchedulerTask* task) +{ + bool do_signal = false; + eventLock.lock(); + + if (getState() == THREAD_STATE_RUNNING) { + // check if the event has a valid id + if (task->getEventId() == 0) { + // if not generate one + if (++lastEventId == 0) { + lastEventId = 1; + } + + task->setEventId(lastEventId); + } + + // insert the event id in the list of active events + eventIds.insert(task->getEventId()); + + // add the event to the queue + eventList.push(task); + + // if the list was empty or this event is the top in the list + // we have to signal it + do_signal = (task == eventList.top()); + } else { + eventLock.unlock(); + delete task; + return 0; + } + + eventLock.unlock(); + + if (do_signal) { + eventSignal.notify_one(); + } + + return task->getEventId(); +} + +bool Scheduler::stopEvent(uint32_t eventid) +{ + if (eventid == 0) { + return false; + } + + std::lock_guard lockClass(eventLock); + + // search the event id.. + auto it = eventIds.find(eventid); + if (it == eventIds.end()) { + return false; + } + + eventIds.erase(it); + return true; +} + +void Scheduler::shutdown() +{ + setState(THREAD_STATE_TERMINATED); + eventLock.lock(); + + //this list should already be empty + while (!eventList.empty()) { + delete eventList.top(); + eventList.pop(); + } + + eventIds.clear(); + eventLock.unlock(); + eventSignal.notify_one(); +} + diff --git a/src/scheduler.h b/src/scheduler.h new file mode 100644 index 0000000..c3c04aa --- /dev/null +++ b/src/scheduler.h @@ -0,0 +1,85 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 +#define FS_SCHEDULER_H_2905B3D5EAB34B4BA8830167262D2DC1 + +#include "tasks.h" +#include +#include + +#include "thread_holder_base.h" + +static constexpr int32_t SCHEDULER_MINTICKS = 50; + +class SchedulerTask : public Task +{ + public: + void setEventId(uint32_t id) { + eventId = id; + } + uint32_t getEventId() const { + return eventId; + } + + std::chrono::system_clock::time_point getCycle() const { + return expiration; + } + + protected: + SchedulerTask(uint32_t delay, const std::function& f) : Task(delay, f) {} + + uint32_t eventId = 0; + + friend SchedulerTask* createSchedulerTask(uint32_t, const std::function&); +}; + +inline SchedulerTask* createSchedulerTask(uint32_t delay, const std::function& f) +{ + return new SchedulerTask(delay, f); +} + +struct TaskComparator { + bool operator()(const SchedulerTask* lhs, const SchedulerTask* rhs) const { + return lhs->getCycle() > rhs->getCycle(); + } +}; + +class Scheduler : public ThreadHolder +{ + public: + uint32_t addEvent(SchedulerTask* task); + bool stopEvent(uint32_t eventId); + + void shutdown(); + + void threadMain(); + protected: + std::thread thread; + std::mutex eventLock; + std::condition_variable eventSignal; + + uint32_t lastEventId {0}; + std::priority_queue, TaskComparator> eventList; + std::unordered_set eventIds; +}; + +extern Scheduler g_scheduler; + +#endif diff --git a/src/script.cpp b/src/script.cpp new file mode 100644 index 0000000..2e0e3cb --- /dev/null +++ b/src/script.cpp @@ -0,0 +1,432 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2017 Alejandro Mujica +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "otpch.h" + +#include "script.h" + +void ScriptReader::nextToken() +{ + signed int v1; // esi@3 + int v4; // eax@5 + int v5; // eax@8 + int v6; // edi@8 + int v7; // eax@24 + int v9; // eax@32 + int v10; // eax@36 + int v11; // edi@36 + int v12; // eax@45 + int v13; // edi@45 + int v14; // eax@52 + int v15; // edi@52 + int v16; // eax@55 + int v17; // eax@62 + int v18; // eax@70 + int v19; // eax@78 + int v20; // edi@78 + int v21; // eax@86 + int v22; // eax@90 + int v23; // edi@90 + int v24; // eax@94 + int v25; // eax@98 + int v26; // edi@98 + int v27; // eax@102 + int v28; // eax@121 + int v29; // eax@127 + int v30; // eax@131 + int v31; // eax@136 + int v32; // eax@139 + std::string FileName; // [sp+1Ch] [bp-1Ch]@2 + int Sign; // [sp+20h] [bp-18h]@5 + FILE *f; // [sp+24h] [bp-14h]@5 + int pos; // [sp+28h] [bp-10h]@3 + + if (this->RecursionDepth == -1) + { + error("ScriptReader::nextToken: Kein Skript zum Lesen ge÷ffnet.\n"); + LABEL_30: + this->Token = ENDOFFILE; + return; + } + FileName = String; +LABEL_3: + pos = 0; + v1 = 0; + this->String = ""; + v4 = this->RecursionDepth; + this->Number = 0; + Sign = 1; + f = this->File[v4]; + while (2) + { + if (pos == 3999) + error("string too long"); + switch (v1) + { + case 0: + v5 = getc(f); + v6 = v5; + if (v5 == -1) + goto LABEL_24; + if (v5 == 10) + { + ++this->Line[this->RecursionDepth]; + } else if (!isspace(v5)) + { + v1 = 1; + if (v6 != 35) + { + v1 = 30; + if (v6 != 64) + { + if (isalpha(v6)) + { + v1 = 2; + this->String += v6; + pos++; + } else if (IsDigit(v6)) + { + this->Number = v6 - 48; + v1 = 3; + } else + { + v1 = 6; + if (v6 != 34) + { + v1 = 11; + if (v6 != 91) + { + v1 = 22; + if (v6 != 60) + { + v1 = 25; + if (v6 != 62) + { + v1 = 27; + if (v6 != 45) + { + v1 = 10; + this->Special = v6; + } + } + } + } + } + } + } + } + } + continue; + case 1: + v9 = getc(f); + if (v9 != -1) + { + if (v9 == 10) + { + ++this->Line[this->RecursionDepth]; + LABEL_35: + v1 = 0; + } + continue; + } + LABEL_24: + v7 = this->RecursionDepth; + if (v7 <= 0) + goto LABEL_30; + if (v7 == -1) + { + printf("ScriptReader::close: Keine Datei offen.\n"); + } else + { + if (fclose(this->File[v7])) + { + printf("ScriptReader::close: Fehler %d beim Schlie¯en der Datei.\n", RecursionDepth); + } + --this->RecursionDepth; + } + goto LABEL_3; + case 2: + v10 = getc(f); + v11 = v10; + if (pos == 30) + error("identifier too long"); + if (v10 == -1) + goto LABEL_43; + if (isalpha(v10) || IsDigit(v11) || v11 == 95) + goto LABEL_39; + ungetc(v11, f); + LABEL_43: + this->Token = IDENTIFIER; + return; + case 3: + v12 = getc(f); + v13 = v12; + if (v12 == -1) + goto LABEL_50; + if (IsDigit(v12)) + goto LABEL_51; + if (v13 == 45) + goto LABEL_48; + ungetc(v13, f); + LABEL_50: + this->Token = NUMBER; + return; + case 4: + v14 = getc(f); + v15 = v14; + if (v14 == -1) + error("unexpected end of file"); + if (!IsDigit(v14)) + error("syntax error"); + v1 = 5; + this->Number = v15 - 48; + continue; + case 5: + v16 = getc(f); + v13 = v16; + if (v16 == -1) + goto LABEL_59; + if (IsDigit(v16)) + goto LABEL_51; + if (v13 != 45) + { + ungetc(v13, f); + LABEL_59: + this->Bytes[pos] = this->Number; + this->Token = BYTES; + return; + } + LABEL_48: + this->Bytes[pos++] = this->Number; + v1 = 4; + continue; + case 6: + v17 = getc(f); + v11 = v17; + if (v17 == -1) + error("unexpected end of file"); + if (v17 == 34) + { + v1 = 8; + } else if (v17 == 92) + { + v1 = 7; + } else + { + if (v17 == 10) + ++this->Line[this->RecursionDepth]; + LABEL_39: + this->String += v11; + pos++; + } + continue; + case 7: + v18 = getc(f); + if (v18 == -1) + error("unexpected end of file"); + if (v18 == 110) { + this->String += 10; + pos++; + } else { + this->String += v18; + pos++; + } + v1 = 6; + continue; + case 8: + this->Token = STRING; + return; + case 10: + goto LABEL_77; + case 11: + v19 = getc(f); + this->Special = 91; + v20 = v19; + if (v19 == -1) + goto LABEL_77; + if (IsDigit(v19)) + { + Sign = 1; + this->Number = v20 - 48; + LABEL_82: + v1 = 12; + continue; + } + if (v20 == 45) + { + Sign = -1; + this->Number = 0; + goto LABEL_82; + } + LABEL_83: + ungetc(v20, f); + LABEL_77: + this->Token = SPECIAL; + return; + case 12: + v21 = getc(f); + v13 = v21; + if (v21 == -1) + error("unexpected end of file"); + if (IsDigit(v21)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 13; + this->CoordX = this->Number * Sign; + continue; + case 13: + v22 = getc(f); + v23 = v22; + if (v22 == -1) + error("unexpected end of file"); + if (IsDigit(v22)) + { + Sign = 1; + this->Number = v23 - 48; + } else + { + if (v23 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 14; + continue; + case 14: + v24 = getc(f); + v13 = v24; + if (v24 == -1) + error("unexpected end of file"); + if (IsDigit(v24)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 15; + this->CoordY = this->Number * Sign; + continue; + case 15: + v25 = getc(f); + v26 = v25; + if (v25 == -1) + error("unexpected end of file"); + if (IsDigit(v25)) + { + Sign = 1; + this->Number = v26 - 48; + } else + { + if (v26 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 16; + continue; + case 16: + v27 = getc(f); + v13 = v27; + if (v27 == -1) + error("unexpected end of file"); + if (IsDigit(v27)) + { + LABEL_51: + this->Number = v13 + 10 * this->Number - 48; + } else + { + if (v13 != 93) + error("syntax error"); + v1 = 17; + this->CoordZ = this->Number * Sign; + } + continue; + case 17: + this->Token = COORDINATE; + return; + case 22: + v28 = getc(f); + this->Special = 60; + if (v28 == -1) + goto LABEL_77; + v1 = 23; + if (v28 == 61) + continue; + v1 = 24; + if (v28 == 62) + continue; + ungetc(v28, f); + goto LABEL_77; + case 23: + this->Special = 76; + goto LABEL_77; + case 24: + this->Special = 78; + goto LABEL_77; + case 25: + v29 = getc(f); + this->Special = 62; + v20 = v29; + if (v29 == -1) + goto LABEL_77; + v1 = 26; + if (v29 != 61) + goto LABEL_83; + continue; + case 26: + this->Special = 71; + goto LABEL_77; + case 27: + v30 = getc(f); + this->Special = 45; + if (v30 == -1) + goto LABEL_77; + v1 = 28; + if (v30 == 62) + continue; + ungetc(v30, f); + goto LABEL_77; + case 28: + this->Special = 73; + goto LABEL_77; + default: + error("ScriptReader::nextToken: Ungnltiger Zustand.\n"); + goto LABEL_35; + case 30: + v31 = getc(f); + if (v31 == -1) + error("unexpected end of file"); + if (v31 != 34) + error("syntax error"); + v1 = 31; + continue; + case 31: + v32 = getc(f); + v11 = v32; + if (v32 == -1) + error("unexpected end of file"); + if (v32 != 34) + goto LABEL_39; + v1 = 32; + continue; + case 32: + open(CurrentDirectory + "/" + String); + goto LABEL_3; + } + } +} diff --git a/src/script.h b/src/script.h new file mode 100644 index 0000000..d686132 --- /dev/null +++ b/src/script.h @@ -0,0 +1,277 @@ +/** +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2017 Alejandro Mujica +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 +#define FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 + +#include "tools.h" + +enum TOKEN +{ + ENDOFFILE = 0, + IDENTIFIER, + NUMBER, + STRING, + BYTES, + COORDINATE, + SPECIAL +}; + +class ScriptReader +{ +public: + ScriptReader() + { + Token = ENDOFFILE; + RecursionDepth = -1; + } + + ~ScriptReader() + { + if (RecursionDepth != -1) + { + std::cout << "ScriptReader::~ScriptReader: File is still open.\n"; + for (int i = RecursionDepth; i != -1; i = RecursionDepth) + { + if (fclose(File[i])) + { + std::cout << "ScriptReader::close: Error when closing the file.\n"; + } + --RecursionDepth; + } + } + } + + TOKEN Token; + FILE* File[3]; + int RecursionDepth; + char Filename[3][4096]; + std::string CurrentDirectory; + std::string String; + unsigned char Bytes[1000]; + int Line[3]; + int Number; + uint16_t CoordX; + uint16_t CoordY; + uint8_t CoordZ; + char Special; + + bool open(const std::string& FileName) + { + RecursionDepth++; + if (RecursionDepth == 3) + { + error("ScriptReader::open: too big recursion.\n"); + error("Recursion depth too high.\n"); + return false; + } + + if (RecursionDepth > -1) + { + CurrentDirectory = FileName; + if (FileName.find('/') != std::string::npos) { + int32_t end = FileName.find_last_of('/'); + CurrentDirectory = FileName.substr(0, end); + strcpy(Filename[RecursionDepth], FileName.substr(end + 1, FileName.length() - end).c_str()); + } else { + strcpy(Filename[RecursionDepth], FileName.c_str()); + } + + File[RecursionDepth] = fopen(FileName.c_str(), "rb"); + if (!File[RecursionDepth]) + { + printf("ScriptReader::open: Can not open file %s.\n", FileName.c_str()); + RecursionDepth--; + printf("Cannot open script-file\n"); + return false; + } + } + + Line[RecursionDepth] = 1; + return true; + } + + void close() + { + int depth; // eax@1 + + depth = RecursionDepth; + if (depth == -1) + { + std::cout << "ScriptReader::close: Invalid recursion depth.\n"; + } else + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --RecursionDepth; + } + } + + void error(const std::string& text) + { + int depth = this->RecursionDepth; + if (depth != -1) + { + printf("error in script-file \"%s\", line %d: %s\n", Filename[this->RecursionDepth], this->Line[this->RecursionDepth], text.c_str()); + do + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --this->RecursionDepth; + depth = this->RecursionDepth; + } while (this->RecursionDepth != -1); + } + } + + void nextToken(); + + std::string readIdentifier() + { + nextToken(); + if (this->Token != IDENTIFIER) + error("identifier expected"); + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return std::string(this->String); + } + + int readNumber() + { + TOKEN v1; // edx@1 + int v2; // esi@1 + + nextToken(); + v1 = this->Token; + v2 = 1; + if (this->Token == SPECIAL && this->Special == 45) + { + v2 = -1; + nextToken(); + v1 = this->Token; + } + if (v1 != NUMBER) + error("number expected"); + if (this->Token != NUMBER) + error("number expected"); + return this->Number * v2; + } + + std::string readString() + { + nextToken(); + if (this->Token != STRING) + error("string expected"); + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* readBytesequence() + { + nextToken(); + if (this->Token != 4) + error("byte-sequence expected"); + if (this->Token != 4) + error("byte-sequence expected"); + return this->Bytes; + } + + void readCoordinate(uint16_t& x, uint16_t& y, uint8_t& z) + { + nextToken(); + if (this->Token != COORDINATE) + error("coordinates expected"); + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int readSpecial() + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } + + void readSymbol(char Symbol) + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (Symbol != this->Special) + error("special-char expected"); + } + + std::string getIdentifier() + { + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return this->String; + } + + int getNumber() + { + if (this->Token != NUMBER) + error("number expected"); + return this->Number; + } + + std::string getString() + { + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* getBytesequence() + { + if (this->Token != BYTES) + error("byte-sequence expected"); + return this->Bytes; + } + + void getcoordinate(int32_t& x, int32_t& y, int32_t& z) + { + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int getSpecial() + { + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } +}; + +#endif diff --git a/src/scriptmanager.cpp b/src/scriptmanager.cpp new file mode 100644 index 0000000..99c24e3 --- /dev/null +++ b/src/scriptmanager.cpp @@ -0,0 +1,97 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "scriptmanager.h" + +#include "actions.h" +#include "chat.h" +#include "talkaction.h" +#include "spells.h" +#include "movement.h" +#include "globalevent.h" + +Actions* g_actions = nullptr; +CreatureEvents* g_creatureEvents = nullptr; +Chat* g_chat = nullptr; +GlobalEvents* g_globalEvents = nullptr; +Spells* g_spells = nullptr; +TalkActions* g_talkActions = nullptr; +MoveEvents* g_moveEvents = nullptr; + +extern LuaEnvironment g_luaEnvironment; + +ScriptingManager::~ScriptingManager() +{ + delete g_spells; + delete g_actions; + delete g_talkActions; + delete g_moveEvents; + delete g_chat; + delete g_creatureEvents; + delete g_globalEvents; +} + +bool ScriptingManager::loadScriptSystems() +{ + if (g_luaEnvironment.loadFile("data/global.lua") == -1) { + std::cout << "[Warning - ScriptingManager::loadScriptSystems] Can not load data/global.lua" << std::endl; + } + + g_chat = new Chat(); + + g_spells = new Spells(); + if (!g_spells->loadFromXml()) { + std::cout << "> ERROR: Unable to load spells!" << std::endl; + return false; + } + + g_actions = new Actions(); + if (!g_actions->loadFromXml()) { + std::cout << "> ERROR: Unable to load actions!" << std::endl; + return false; + } + + g_talkActions = new TalkActions(); + if (!g_talkActions->loadFromXml()) { + std::cout << "> ERROR: Unable to load talk actions!" << std::endl; + return false; + } + + g_moveEvents = new MoveEvents(); + if (!g_moveEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load move events!" << std::endl; + return false; + } + + g_creatureEvents = new CreatureEvents(); + if (!g_creatureEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load creature events!" << std::endl; + return false; + } + + g_globalEvents = new GlobalEvents(); + if (!g_globalEvents->loadFromXml()) { + std::cout << "> ERROR: Unable to load global events!" << std::endl; + return false; + } + + return true; +} diff --git a/src/scriptmanager.h b/src/scriptmanager.h new file mode 100644 index 0000000..0c6fd3f --- /dev/null +++ b/src/scriptmanager.h @@ -0,0 +1,41 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B +#define FS_SCRIPTMANAGER_H_F9428B7803A44FB88EB1A915CFD37F8B + +class ScriptingManager +{ + public: + ScriptingManager() = default; + ~ScriptingManager(); + + // non-copyable + ScriptingManager(const ScriptingManager&) = delete; + ScriptingManager& operator=(const ScriptingManager&) = delete; + + static ScriptingManager* getInstance() { + static ScriptingManager instance; + return &instance; + } + + bool loadScriptSystems(); +}; + +#endif diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..80c72ab --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,204 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "outputmessage.h" +#include "server.h" +#include "scheduler.h" +#include "configmanager.h" +#include "ban.h" + +extern ConfigManager g_config; +Ban g_bans; + +ServiceManager::~ServiceManager() +{ + stop(); +} + +void ServiceManager::die() +{ + io_service.stop(); +} + +void ServiceManager::run() +{ + assert(!running); + running = true; + io_service.run(); +} + +void ServiceManager::stop() +{ + if (!running) { + return; + } + + running = false; + + for (auto& servicePortIt : acceptors) { + try { + io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); + } catch (boost::system::system_error& e) { + std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; + } + } + + acceptors.clear(); + + death_timer.expires_from_now(boost::posix_time::seconds(3)); + death_timer.async_wait(std::bind(&ServiceManager::die, this)); +} + +ServicePort::~ServicePort() +{ + close(); +} + +bool ServicePort::is_single_socket() const +{ + return !services.empty() && services.front()->is_single_socket(); +} + +std::string ServicePort::get_protocol_names() const +{ + if (services.empty()) { + return std::string(); + } + + std::string str = services.front()->get_protocol_name(); + for (size_t i = 1; i < services.size(); ++i) { + str.push_back(','); + str.push_back(' '); + str.append(services[i]->get_protocol_name()); + } + return str; +} + +void ServicePort::accept() +{ + if (!acceptor) { + return; + } + + auto connection = ConnectionManager::getInstance().createConnection(io_service, shared_from_this()); + acceptor->async_accept(connection->getSocket(), std::bind(&ServicePort::onAccept, shared_from_this(), connection, std::placeholders::_1)); +} + +void ServicePort::onAccept(Connection_ptr connection, const boost::system::error_code& error) +{ + if (!error) { + if (services.empty()) { + return; + } + + auto remote_ip = connection->getIP(); + if (remote_ip != 0 && g_bans.acceptConnection(remote_ip)) { + Service_ptr service = services.front(); + if (service->is_single_socket()) { + connection->accept(service->make_protocol(connection)); + } else { + connection->accept(); + } + } else { + connection->close(Connection::FORCE_CLOSE); + } + + accept(); + } else if (error != boost::asio::error::operation_aborted) { + if (!pendingStart) { + close(); + pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); + } + } +} + +Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const +{ + uint8_t protocolID = msg.getByte(); + for (auto& service : services) { + if (protocolID != service->get_protocol_identifier()) { + continue; + } + + return service->make_protocol(connection); + } + return nullptr; +} + +void ServicePort::onStopServer() +{ + close(); +} + +void ServicePort::openAcceptor(std::weak_ptr weak_service, uint16_t port) +{ + if (auto service = weak_service.lock()) { + service->open(port); + } +} + +void ServicePort::open(uint16_t port) +{ + close(); + + serverPort = port; + pendingStart = false; + + try { + if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { + acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); + } else { + acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); + } + + acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); + + accept(); + } catch (boost::system::system_error& e) { + std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; + + pendingStart = true; + g_scheduler.addEvent(createSchedulerTask(15000, + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); + } +} + +void ServicePort::close() +{ + if (acceptor && acceptor->is_open()) { + boost::system::error_code error; + acceptor->close(error); + } +} + +bool ServicePort::add_service(const Service_ptr& new_svc) +{ + if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) { + return false; + } + + services.push_back(new_svc); + return true; +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..a662f1c --- /dev/null +++ b/src/server.h @@ -0,0 +1,150 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SERVER_H_984DA68ABF744127850F90CC710F281B +#define FS_SERVER_H_984DA68ABF744127850F90CC710F281B + +#include "connection.h" +#include + +class Protocol; + +class ServiceBase +{ + public: + virtual bool is_single_socket() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; + + virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; +}; + +template +class Service final : public ServiceBase +{ + public: + bool is_single_socket() const final { + return ProtocolType::server_sends_first; + } + uint8_t get_protocol_identifier() const final { + return ProtocolType::protocol_identifier; + } + const char* get_protocol_name() const final { + return ProtocolType::protocol_name(); + } + + Protocol_ptr make_protocol(const Connection_ptr& c) const final { + return std::make_shared(c); + } +}; + +class ServicePort : public std::enable_shared_from_this +{ + public: + explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} + ~ServicePort(); + + // non-copyable + ServicePort(const ServicePort&) = delete; + ServicePort& operator=(const ServicePort&) = delete; + + static void openAcceptor(std::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; + + bool add_service(const Service_ptr& new_svc); + Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; + + void onStopServer(); + void onAccept(Connection_ptr connection, const boost::system::error_code& error); + + protected: + void accept(); + + boost::asio::io_service& io_service; + std::unique_ptr acceptor; + std::vector services; + + uint16_t serverPort = 0; + bool pendingStart = false; +}; + +class ServiceManager +{ + public: + ServiceManager() = default; + ~ServiceManager(); + + // non-copyable + ServiceManager(const ServiceManager&) = delete; + ServiceManager& operator=(const ServiceManager&) = delete; + + void run(); + void stop(); + + template + bool add(uint16_t port); + + bool is_running() const { + return acceptors.empty() == false; + } + + protected: + void die(); + + std::unordered_map acceptors; + + boost::asio::io_service io_service; + boost::asio::deadline_timer death_timer { io_service }; + bool running = false; +}; + +template +bool ServiceManager::add(uint16_t port) +{ + if (port == 0) { + std::cout << "ERROR: No port provided for service " << ProtocolType::protocol_name() << ". Service disabled." << std::endl; + return false; + } + + ServicePort_ptr service_port; + + auto foundServicePort = acceptors.find(port); + + if (foundServicePort == acceptors.end()) { + service_port = std::make_shared(io_service); + service_port->open(port); + acceptors[port] = service_port; + } else { + service_port = foundServicePort->second; + + if (service_port->is_single_socket() || ProtocolType::server_sends_first) { + std::cout << "ERROR: " << ProtocolType::protocol_name() << + " and " << service_port->get_protocol_names() << + " cannot use the same port " << port << '.' << std::endl; + return false; + } + } + + return service_port->add_service(std::make_shared>()); +} + +#endif diff --git a/src/spawn.cpp b/src/spawn.cpp new file mode 100644 index 0000000..6a9e22a --- /dev/null +++ b/src/spawn.cpp @@ -0,0 +1,344 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "spawn.h" +#include "game.h" +#include "monster.h" +#include "configmanager.h" +#include "scheduler.h" + +#include "pugicast.h" + +extern ConfigManager g_config; +extern Monsters g_monsters; +extern Game g_game; + +static constexpr int32_t MINSPAWN_INTERVAL = 1000; + +bool Spawns::loadFromXml(const std::string& filename) +{ + if (loaded) { + return true; + } + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Spawns::loadFromXml", filename, result); + return false; + } + + this->filename = filename; + loaded = true; + + for (auto spawnNode : doc.child("spawns").children()) { + Position centerPos( + pugi::cast(spawnNode.attribute("centerx").value()), + pugi::cast(spawnNode.attribute("centery").value()), + pugi::cast(spawnNode.attribute("centerz").value()) + ); + + int32_t radius; + pugi::xml_attribute radiusAttribute = spawnNode.attribute("radius"); + if (radiusAttribute) { + radius = pugi::cast(radiusAttribute.value()); + } else { + radius = -1; + } + + for (auto childNode : spawnNode.children()) { + if (strcasecmp(childNode.name(), "monster") == 0) { + pugi::xml_attribute nameAttribute = childNode.attribute("name"); + if (!nameAttribute) { + continue; + } + + Direction dir; + + pugi::xml_attribute directionAttribute = childNode.attribute("direction"); + if (directionAttribute) { + dir = static_cast(pugi::cast(directionAttribute.value())); + } else { + dir = DIRECTION_NORTH; + } + + Position pos( + centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), + centerPos.z + ); + + spawnList.emplace_front(pos, radius); + Spawn& spawn = spawnList.front(); + + uint32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; + if (interval > MINSPAWN_INTERVAL) { + uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN); + if (exInterval) { + spawn.addMonster(nameAttribute.as_string(), pos, dir, exInterval * 1000); + } else { + spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); + } + } else { + std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; + } + } else if (strcasecmp(childNode.name(), "npc") == 0) { + pugi::xml_attribute nameAttribute = childNode.attribute("name"); + if (!nameAttribute) { + continue; + } + + Npc* npc = Npc::createNpc(nameAttribute.as_string()); + if (!npc) { + continue; + } + + pugi::xml_attribute directionAttribute = childNode.attribute("direction"); + if (directionAttribute) { + npc->setDirection(static_cast(pugi::cast(directionAttribute.value()))); + } + + npc->setMasterPos(Position( + centerPos.x + pugi::cast(childNode.attribute("x").value()), + centerPos.y + pugi::cast(childNode.attribute("y").value()), + centerPos.z + ), radius); + npcList.push_front(npc); + } + } + } + return true; +} + +void Spawns::startup() +{ + if (!loaded || isStarted()) { + return; + } + + for (Npc* npc : npcList) { + g_game.placeCreature(npc, npc->getMasterPos(), false, true); + } + npcList.clear(); + + for (Spawn& spawn : spawnList) { + spawn.startup(); + } + + started = true; +} + +void Spawns::clear() +{ + for (Spawn& spawn : spawnList) { + spawn.stopEvent(); + } + spawnList.clear(); + + loaded = false; + started = false; + filename.clear(); +} + +bool Spawns::isInZone(const Position& centerPos, int32_t radius, const Position& pos) +{ + if (radius == -1) { + return true; + } + + return ((pos.getX() >= centerPos.getX() - radius) && (pos.getX() <= centerPos.getX() + radius) && + (pos.getY() >= centerPos.getY() - radius) && (pos.getY() <= centerPos.getY() + radius)); +} + +void Spawn::startSpawnCheck() +{ + if (checkSpawnEvent == 0) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + } +} + +Spawn::~Spawn() +{ + for (const auto& it : spawnedMap) { + Monster* monster = it.second; + monster->setSpawn(nullptr); + monster->decrementReferenceCounter(); + } +} + +bool Spawn::findPlayer(const Position& pos) +{ + SpectatorVec list; + g_game.map.getSpectators(list, pos, false, true); + for (Creature* spectator : list) { + if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { + return true; + } + } + return false; +} + +bool Spawn::isInSpawnZone(const Position& pos) +{ + return Spawns::isInZone(centerPos, radius, pos); +} + +bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup /*= false*/) +{ + std::unique_ptr monster_ptr(new Monster(mType)); + if (startup) { + //No need to send out events to the surrounding since there is no one out there to listen! + if (!g_game.internalPlaceCreature(monster_ptr.get(), pos, true)) { + return false; + } + } else { + if (!g_game.placeCreature(monster_ptr.get(), pos, false, true)) { + return false; + } + } + + Monster* monster = monster_ptr.release(); + monster->setDirection(dir); + monster->setSpawn(this); + monster->setMasterPos(pos); + monster->incrementReferenceCounter(); + + spawnedMap.insert(spawned_pair(spawnId, monster)); + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + return true; +} + +uint32_t Spawn::getInterval() const +{ + uint32_t newInterval = interval; + + if (newInterval > 500000) { + size_t playersOnline = g_game.getPlayersOnline(); + if (playersOnline <= 800) { + if (playersOnline > 200) { + newInterval = 200 * interval / (playersOnline / 2 + 100); + } + } else { + newInterval = 2 * interval / 5; + } + + return normal_random(newInterval / 2, newInterval); + } + + return newInterval; +} + +void Spawn::startup() +{ + for (const auto& it : spawnMap) { + uint32_t spawnId = it.first; + const spawnBlock_t& sb = it.second; + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction, true); + } +} + +void Spawn::checkSpawn() +{ + checkSpawnEvent = 0; + + cleanup(); + + for (auto& it : spawnMap) { + uint32_t spawnId = it.first; + if (spawnedMap.find(spawnId) != spawnedMap.end()) { + continue; + } + + spawnBlock_t& sb = it.second; + if (OTSYS_TIME() >= sb.lastSpawn + sb.interval) { + if (findPlayer(sb.pos)) { + sb.lastSpawn = OTSYS_TIME(); + continue; + } + + spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); + } + } + + if (spawnedMap.size() < spawnMap.size()) { + checkSpawnEvent = g_scheduler.addEvent(createSchedulerTask(getInterval(), std::bind(&Spawn::checkSpawn, this))); + } +} + +void Spawn::cleanup() +{ + auto it = spawnedMap.begin(); + while (it != spawnedMap.end()) { + uint32_t spawnId = it->first; + Monster* monster = it->second; + if (monster->isRemoved()) { + if (spawnId != 0) { + spawnMap[spawnId].lastSpawn = OTSYS_TIME(); + } + + monster->decrementReferenceCounter(); + it = spawnedMap.erase(it); + } else { + ++it; + } + } +} + +bool Spawn::addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval) +{ + MonsterType* mType = g_monsters.getMonsterType(name); + if (!mType) { + std::cout << "[Spawn::addMonster] Can not find " << name << std::endl; + return false; + } + + this->interval = std::min(this->interval, interval); + + spawnBlock_t sb; + sb.mType = mType; + sb.pos = pos; + sb.direction = dir; + sb.interval = interval; + sb.lastSpawn = 0; + + uint32_t spawnId = spawnMap.size() + 1; + spawnMap[spawnId] = sb; + return true; +} + +void Spawn::removeMonster(Monster* monster) +{ + for (auto it = spawnedMap.begin(), end = spawnedMap.end(); it != end; ++it) { + if (it->second == monster) { + monster->decrementReferenceCounter(); + spawnedMap.erase(it); + break; + } + } +} + +void Spawn::stopEvent() +{ + if (checkSpawnEvent != 0) { + g_scheduler.stopEvent(checkSpawnEvent); + checkSpawnEvent = 0; + } +} diff --git a/src/spawn.h b/src/spawn.h new file mode 100644 index 0000000..bcacfce --- /dev/null +++ b/src/spawn.h @@ -0,0 +1,101 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B +#define FS_SPAWN_H_1A86089E080846A9AE53ED12E7AE863B + +#include "tile.h" +#include "position.h" + +class Monster; +class MonsterType; +class Npc; + +struct spawnBlock_t { + Position pos; + MonsterType* mType; + int64_t lastSpawn; + uint32_t interval; + Direction direction; +}; + +class Spawn +{ + public: + Spawn(Position pos, int32_t radius) : centerPos(std::move(pos)), radius(radius) {} + ~Spawn(); + + // non-copyable + Spawn(const Spawn&) = delete; + Spawn& operator=(const Spawn&) = delete; + + bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); + void removeMonster(Monster* monster); + + uint32_t getInterval() const; + void startup(); + + void startSpawnCheck(); + void stopEvent(); + + bool isInSpawnZone(const Position& pos); + void cleanup(); + + private: + //map of the spawned creatures + typedef std::multimap SpawnedMap; + typedef SpawnedMap::value_type spawned_pair; + SpawnedMap spawnedMap; + + //map of creatures in the spawn + std::map spawnMap; + + Position centerPos; + int32_t radius; + + uint32_t interval = 60000; + uint32_t checkSpawnEvent = 0; + + static bool findPlayer(const Position& pos); + bool spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& pos, Direction dir, bool startup = false); + void checkSpawn(); +}; + +class Spawns +{ + public: + static bool isInZone(const Position& centerPos, int32_t radius, const Position& pos); + + bool loadFromXml(const std::string& filename); + void startup(); + void clear(); + + bool isStarted() const { + return started; + } + + private: + std::forward_list npcList; + std::forward_list spawnList; + std::string filename; + bool loaded = false; + bool started = false; +}; + +#endif diff --git a/src/spells.cpp b/src/spells.cpp new file mode 100644 index 0000000..9f391fd --- /dev/null +++ b/src/spells.cpp @@ -0,0 +1,1896 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "combat.h" +#include "configmanager.h" +#include "game.h" +#include "monster.h" +#include "pugicast.h" +#include "spells.h" + +extern Game g_game; +extern Spells* g_spells; +extern Monsters g_monsters; +extern Vocations g_vocations; +extern ConfigManager g_config; +extern LuaEnvironment g_luaEnvironment; + +Spells::Spells() +{ + scriptInterface.initState(); +} + +Spells::~Spells() +{ + clear(); +} + +TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) +{ + std::string str_words = words; + + //strip trailing spaces + trimString(str_words); + + std::ostringstream str_instantSpell; + for (size_t i = 0; i < str_words.length(); i++) { + if (!isspace(str_words[i]) || (i < str_words.length() - 1 && !isspace(str_words[i + 1]))) { + str_instantSpell << str_words[i]; + } + } + + str_words = str_instantSpell.str(); + + InstantSpell* instantSpell = getInstantSpell(str_words); + if (!instantSpell) { + return TALKACTION_CONTINUE; + } + + std::string param; + + if (instantSpell->getHasParam()) { + size_t spellLen = instantSpell->getWords().length(); + size_t paramLen = str_words.length() - spellLen; + std::string paramText = str_words.substr(spellLen, paramLen); + if (!paramText.empty() && paramText.front() == ' ') { + size_t loc1 = paramText.find('"', 1); + if (loc1 != std::string::npos) { + size_t loc2 = paramText.find('"', loc1 + 1); + if (loc2 == std::string::npos) { + loc2 = paramText.length(); + } else if (paramText.find_last_not_of(' ') != loc2) { + return TALKACTION_CONTINUE; + } + + param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); + } else { + trimString(paramText); + loc1 = paramText.find(' ', 0); + if (loc1 == std::string::npos) { + param = paramText; + } else { + return TALKACTION_CONTINUE; + } + } + } + } + + if (instantSpell->playerCastInstant(player, param)) { + return TALKACTION_BREAK; + } + + return TALKACTION_FAILED; +} + +void Spells::clear() +{ + for (const auto& it : runes) { + delete it.second; + } + runes.clear(); + + for (const auto& it : instants) { + delete it.second; + } + instants.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& Spells::getScriptInterface() +{ + return scriptInterface; +} + +std::string Spells::getScriptBaseName() const +{ + return "spells"; +} + +Event* Spells::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "rune") == 0) { + return new RuneSpell(&scriptInterface); + } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { + return new InstantSpell(&scriptInterface); + } else if (strcasecmp(nodeName.c_str(), "conjure") == 0) { + return new ConjureSpell(&scriptInterface); + } + return nullptr; +} + +bool Spells::registerEvent(Event* event, const pugi::xml_node&) +{ + InstantSpell* instant = dynamic_cast(event); + if (instant) { + auto result = instants.emplace(instant->getWords(), instant); + if (!result.second) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; + } + return result.second; + } + + RuneSpell* rune = dynamic_cast(event); + if (rune) { + auto result = runes.emplace(rune->getRuneItemId(), rune); + if (!result.second) { + std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; + } + return result.second; + } + + return false; +} + +Spell* Spells::getSpellByName(const std::string& name) +{ + Spell* spell = getRuneSpellByName(name); + if (!spell) { + spell = getInstantSpellByName(name); + } + return spell; +} + +RuneSpell* Spells::getRuneSpell(uint32_t id) +{ + auto it = runes.find(id); + if (it == runes.end()) { + return nullptr; + } + return it->second; +} + +RuneSpell* Spells::getRuneSpellByName(const std::string& name) +{ + for (const auto& it : runes) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +InstantSpell* Spells::getInstantSpell(const std::string& words) +{ + InstantSpell* result = nullptr; + + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + + const std::string& instantSpellWords = instantSpell->getWords(); + size_t spellLen = instantSpellWords.length(); + if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { + if (!result || spellLen > result->getWords().length()) { + result = instantSpell; + if (words.length() == spellLen) { + break; + } + } + } + } + + if (result) { + const std::string& resultWords = result->getWords(); + if (words.length() > resultWords.length()) { + size_t spellLen = resultWords.length(); + size_t paramLen = words.length() - spellLen; + if (paramLen < 2 || (words[spellLen] != ' ' || words[spellLen + 1] != '"')) { + return nullptr; + } + } + + return result; + } + + return nullptr; +} + +uint32_t Spells::getInstantSpellCount(const Player* player) const +{ + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + ++count; + } + } + return count; +} + +InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) +{ + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + if (count == index) { + return instantSpell; + } + ++count; + } + } + return nullptr; +} + +InstantSpell* Spells::getInstantSpellByName(const std::string& name) +{ + for (const auto& it : instants) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; + } + } + return nullptr; +} + +Position Spells::getCasterPosition(Creature* creature, Direction dir) +{ + return getNextPosition(dir, creature->getPosition()); +} + +CombatSpell::CombatSpell(Combat* combat, bool needTarget, bool needDirection) : + Event(&g_spells->getScriptInterface()), + combat(combat), + needDirection(needDirection), + needTarget(needTarget) +{} + +CombatSpell::~CombatSpell() +{ + if (!scripted) { + delete combat; + } +} + +bool CombatSpell::loadScriptCombat() +{ + combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId); + return combat != nullptr; +} + +bool CombatSpell::castSpell(Creature* creature) +{ + if (scripted) { + LuaVariant var; + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + + return executeCastSpell(creature, var); + } + + Position pos; + if (needDirection) { + pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + pos = creature->getPosition(); + } + + combat->doCombat(creature, pos); + return true; +} + +bool CombatSpell::castSpell(Creature* creature, Creature* target) +{ + if (scripted) { + LuaVariant var; + + if (combat->hasArea()) { + var.type = VARIANT_POSITION; + + if (needTarget) { + var.pos = target->getPosition(); + } else if (needDirection) { + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.pos = creature->getPosition(); + } + } else { + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } + return executeCastSpell(creature, var); + } + + if (combat->hasArea()) { + if (needTarget) { + combat->doCombat(creature, target->getPosition()); + } else { + return castSpell(creature); + } + } else { + combat->doCombat(creature, target); + } + return true; +} + +bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(creature, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + return scriptInterface->callFunction(2); +} + +bool Spell::configureSpell(const pugi::xml_node& node) +{ + pugi::xml_attribute nameAttribute = node.attribute("name"); + if (!nameAttribute) { + std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl; + return false; + } + + name = nameAttribute.as_string(); + + /*static const char* reservedList[] = { + "melee", + "physical", + "poison", + "fire", + "energy", + "drown", + "lifedrain", + "manadrain", + "healing", + "speed", + "outfit", + "invisible", + "drunk", + "firefield", + "poisonfield", + "energyfield", + "firecondition", + "poisoncondition", + "energycondition", + }; + + //static size_t size = sizeof(reservedList) / sizeof(const char*); + //for (size_t i = 0; i < size; ++i) { + for (const char* reserved : reservedList) { + if (strcasecmp(reserved, name.c_str()) == 0) { + std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; + return false; + } + }*/ + + pugi::xml_attribute attr; + if ((attr = node.attribute("spellid"))) { + spellId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("lvl"))) { + level = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("maglv"))) { + magLevel = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("mana"))) { + mana = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("manapercent"))) { + manaPercent = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("soul"))) { + soul = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("range"))) { + range = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) { + cooldown = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("prem"))) { + premium = attr.as_bool(); + } + + if ((attr = node.attribute("enabled"))) { + enabled = attr.as_bool(); + } + + if ((attr = node.attribute("needtarget"))) { + needTarget = attr.as_bool(); + } + + if ((attr = node.attribute("needweapon"))) { + needWeapon = attr.as_bool(); + } + + if ((attr = node.attribute("selftarget"))) { + selfTarget = attr.as_bool(); + } + + if ((attr = node.attribute("needlearn"))) { + learnable = attr.as_bool(); + } + + if ((attr = node.attribute("blocking"))) { + blockingSolid = attr.as_bool(); + blockingCreature = blockingSolid; + } + + if ((attr = node.attribute("blocktype"))) { + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + if (tmpStrValue == "all") { + blockingSolid = true; + blockingCreature = true; + } else if (tmpStrValue == "solid") { + blockingSolid = true; + } else if (tmpStrValue == "creature") { + blockingCreature = true; + } else { + std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl; + } + } + + if ((attr = node.attribute("aggressive"))) { + aggressive = booleanString(attr.as_string()); + } + + for (auto vocationNode : node.children()) { + if (!(attr = vocationNode.attribute("name"))) { + continue; + } + + int32_t vocationId = g_vocations.getVocationId(attr.as_string()); + if (vocationId != -1) { + attr = vocationNode.attribute("showInDescription"); + vocSpellMap[vocationId] = !attr || attr.as_bool(); + } else { + std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl; + } + } + return true; +} + +bool Spell::playerSpellCheck(Player* player) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (!enabled) { + return false; + } + + if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); + return false; + } + + if (player->hasCondition(CONDITION_EXHAUST)) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + + if (isInstant()) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + + return false; + } + + if (player->getLevel() < level) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMagicLevel() < magLevel) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (isInstant() && isLearnable()) { + if (!player->hasLearnedInstantSpell(getName())) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) { + player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (needWeapon) { + Item* weapon = player->getWeapon(); + if (!weapon) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + switch (weapon->getWeaponType()) { + case WEAPON_SWORD: + case WEAPON_CLUB: + case WEAPON_AXE: + break; + + default: { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + } + + if (isPremium() && !player->isPremium()) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + return true; +} + +bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) +{ + if (toPos.x == 0xFFFF) { + return true; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile) { + tile = new StaticTile(toPos.x, toPos.y, toPos.z); + g_game.map.setTile(toPos, tile); + } + + ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + return true; +} + +bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (toPos.x == 0xFFFF) { + return true; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z > toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } else if (playerPos.z < toPos.z) { + player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Tile* tile = g_game.map.getTile(toPos); + if (!tile) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { + player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + const Creature* topVisibleCreature = tile->getTopCreature(); + if (blockingCreature && topVisibleCreature) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (needTarget && !topVisibleCreature) { + player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { + const Player* targetPlayer = topVisibleCreature->getPlayer(); + if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { + player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + return true; +} + +void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const +{ + if (finishedCast) { + if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { + if (aggressive) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 2000); + player->addCondition(condition); + } + } else { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 1000); + player->addCondition(condition); + } + } + } + + if (aggressive) { + player->addInFightTicks(); + } + } + + if (payCost) { + Spell::postCastSpell(player, getManaCost(player), getSoulCost()); + } +} + +void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) +{ + if (manaCost > 0) { + player->addManaSpent(manaCost); + player->changeMana(-static_cast(manaCost)); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) { + if (soulCost > 0) { + player->changeSoul(-static_cast(soulCost)); + } + } +} + +uint32_t Spell::getManaCost(const Player* player) const +{ + if (mana != 0) { + return mana; + } + + if (manaPercent != 0) { + return player->getLevel() * manaPercent; + } + + return 0; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) +{ + ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); + outfitCondition->setOutfit(outfit); + creature->addCondition(outfitCondition); + return RETURNVALUE_NOERROR; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) +{ + const auto mType = g_monsters.getMonsterType(name); + if (mType == nullptr) { + return RETURNVALUE_CREATUREDOESNOTEXIST; + } + + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { + if (!mType->info.isIllusionable) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + return CreateIllusion(creature, mType->info.outfit, time); +} + +ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) +{ + const ItemType& it = Item::items[itemId]; + if (it.id == 0) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Outfit_t outfit; + outfit.lookTypeEx = itemId; + + return CreateIllusion(creature, outfit, time); +} + +std::string InstantSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool InstantSpell::configureEvent(const pugi::xml_node& node) +{ + if (!Spell::configureSpell(node)) { + return false; + } + + if (!TalkAction::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("params"))) { + hasParam = attr.as_bool(); + } + + if ((attr = node.attribute("playernameparam"))) { + hasPlayerNameParam = attr.as_bool(); + } + + if ((attr = node.attribute("direction"))) { + needDirection = attr.as_bool(); + } else if ((attr = node.attribute("casterTargetOrDirection"))) { + casterTargetOrDirection = attr.as_bool(); + } + + if ((attr = node.attribute("blockwalls"))) { + checkLineOfSight = attr.as_bool(); + } + return true; +} + +bool InstantSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "edithouseguest") == 0) { + function = HouseGuestList; + } else if (strcasecmp(functionName, "edithousesubowner") == 0) { + function = HouseSubOwnerList; + } else if (strcasecmp(functionName, "edithousedoor") == 0) { + function = HouseDoorList; + } else if (strcasecmp(functionName, "housekick") == 0) { + function = HouseKick; + } else if (strcasecmp(functionName, "searchplayer") == 0) { + function = SearchPlayer; + } else if (strcasecmp(functionName, "levitate") == 0) { + function = Levitate; + } else if (strcasecmp(functionName, "illusion") == 0) { + function = Illusion; + } else if (strcasecmp(functionName, "summonmonster") == 0) { + function = SummonMonster; + } else { + std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +bool InstantSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + LuaVariant var; + + if (selfTarget) { + var.type = VARIANT_NUMBER; + var.number = player->getID(); + } else if (needTarget || casterTargetOrDirection) { + Creature* target = nullptr; + bool useDirection = false; + + if (hasParam) { + Player* playerTarget = nullptr; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) { + playerTarget = nullptr; + } + + target = playerTarget; + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + useDirection = true; + } + + if (playerTarget) { + param = playerTarget->getName(); + } + } else { + target = player->getAttackedCreature(); + if (!target || target->getHealth() <= 0) { + if (!casterTargetOrDirection) { + player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + useDirection = true; + } + } + + if (!useDirection) { + if (!canThrowSpell(player, target)) { + player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + } else { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(player, player->getDirection()); + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + } else if (hasParam) { + var.type = VARIANT_STRING; + + if (getHasPlayerNameParam()) { + Player* playerTarget = nullptr; + ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); + + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) { + param = playerTarget->getName(); + } + } + + var.text = param; + } else { + var.type = VARIANT_POSITION; + + if (needDirection) { + var.pos = Spells::getCasterPosition(player, player->getDirection()); + } else { + var.pos = player->getPosition(); + } + + if (!playerInstantSpellCheck(player, var.pos)) { + return false; + } + } + + bool result = internalCastSpell(player, var); + if (result) { + postCastSpell(player); + } + + return result; +} + +bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const +{ + const Position& fromPos = creature->getPosition(); + const Position& toPos = target->getPosition(); + if (fromPos.z != toPos.z || + (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || + (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { + return false; + } + return true; +} + +bool InstantSpell::castSpell(Creature* creature) +{ + LuaVariant var; + + if (casterTargetOrDirection) { + Creature* target = creature->getAttackedCreature(); + if (target && target->getHealth() > 0) { + if (!canThrowSpell(creature, target)) { + return false; + } + + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } + + return false; + } else if (needDirection) { + var.type = VARIANT_POSITION; + var.pos = Spells::getCasterPosition(creature, creature->getDirection()); + } else { + var.type = VARIANT_POSITION; + var.pos = creature->getPosition(); + } + + return internalCastSpell(creature, var); +} + +bool InstantSpell::castSpell(Creature* creature, Creature* target) +{ + if (needTarget) { + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); + } else { + return castSpell(creature); + } +} + +bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) +{ + if (scripted) { + return executeCastSpell(creature, var); + } else if (function) { + return function(this, creature, var.text); + } + + return false; +} + +bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(creature, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + return scriptInterface->callFunction(2); +} + +House* InstantSpell::getHouseFromPos(Creature* creature) +{ + if (!creature) { + return nullptr; + } + + Player* player = creature->getPlayer(); + if (!player) { + return nullptr; + } + + HouseTile* houseTile = dynamic_cast(player->getTile()); + if (!houseTile) { + return nullptr; + } + + House* house = houseTile->getHouse(); + if (!house) { + return nullptr; + } + + return house; +} + +bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(GUEST_LIST, player)) { + player->setEditHouse(house, GUEST_LIST); + player->sendHouseWindow(house, GUEST_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(SUBOWNER_LIST, player)) { + player->setEditHouse(house, SUBOWNER_LIST); + player->sendHouseWindow(house, SUBOWNER_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + Position pos = Spells::getCasterPosition(player, player->getDirection()); + Door* door = house->getDoorByPosition(pos); + if (door && house->canEditAccessList(door->getDoorId(), player)) { + player->setEditHouse(house, door->getDoorId()); + player->sendHouseWindow(house, door->getDoorId()); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + Player* targetPlayer = g_game.getPlayerByName(param); + if (!targetPlayer) { + targetPlayer = player; + } + + House* house = getHouseFromPos(targetPlayer); + if (!house) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + + if (!house->kickPlayer(player, targetPlayer)) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + return true; +} + +bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param) +{ + //a. From 1 to 4 sq's [Person] is standing next to you. + //b. From 5 to 100 sq's [Person] is to the south, north, east, west. + //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. + //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. + //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. + //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + enum distance_t { + DISTANCE_BESIDE, + DISTANCE_CLOSE, + DISTANCE_FAR, + DISTANCE_VERYFAR, + }; + + enum direction_t { + DIR_N, DIR_S, DIR_E, DIR_W, + DIR_NE, DIR_NW, DIR_SE, DIR_SW, + }; + + enum level_t { + LEVEL_HIGHER, + LEVEL_LOWER, + LEVEL_SAME, + }; + + Player* playerExiva = g_game.getPlayerByName(param); + if (!playerExiva) { + return false; + } + + if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { + player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + const Position& lookPos = player->getPosition(); + const Position& searchPos = playerExiva->getPosition(); + + int32_t dx = Position::getOffsetX(lookPos, searchPos); + int32_t dy = Position::getOffsetY(lookPos, searchPos); + int32_t dz = Position::getOffsetZ(lookPos, searchPos); + + distance_t distance; + + direction_t direction; + + level_t level; + + //getting floor + if (dz > 0) { + level = LEVEL_HIGHER; + } else if (dz < 0) { + level = LEVEL_LOWER; + } else { + level = LEVEL_SAME; + } + + //getting distance + if (std::abs(dx) < 4 && std::abs(dy) < 4) { + distance = DISTANCE_BESIDE; + } else { + int32_t distance2 = dx * dx + dy * dy; + if (distance2 < 10000) { + distance = DISTANCE_CLOSE; + } else if (distance2 < 75076) { + distance = DISTANCE_FAR; + } else { + distance = DISTANCE_VERYFAR; + } + } + + //getting direction + float tan; + if (dx != 0) { + tan = static_cast(dy) / dx; + } else { + tan = 10.; + } + + if (std::abs(tan) < 0.4142) { + if (dx > 0) { + direction = DIR_W; + } else { + direction = DIR_E; + } + } else if (std::abs(tan) < 2.4142) { + if (tan > 0) { + if (dy > 0) { + direction = DIR_NW; + } else { + direction = DIR_SE; + } + } else { + if (dx > 0) { + direction = DIR_SW; + } else { + direction = DIR_NE; + } + } + } else { + if (dy > 0) { + direction = DIR_N; + } else { + direction = DIR_S; + } + } + + std::ostringstream ss; + ss << playerExiva->getName(); + + if (distance == DISTANCE_BESIDE) { + if (level == LEVEL_SAME) { + ss << " is standing next to you."; + } else if (level == LEVEL_HIGHER) { + ss << " is above you."; + } else if (level == LEVEL_LOWER) { + ss << " is below you."; + } + } else { + switch (distance) { + case DISTANCE_CLOSE: + if (level == LEVEL_SAME) { + ss << " is to the "; + } else if (level == LEVEL_HIGHER) { + ss << " is on a higher level to the "; + } else if (level == LEVEL_LOWER) { + ss << " is on a lower level to the "; + } + break; + case DISTANCE_FAR: + ss << " is far to the "; + break; + case DISTANCE_VERYFAR: + ss << " is very far to the "; + break; + default: + break; + } + + switch (direction) { + case DIR_N: + ss << "north."; + break; + case DIR_S: + ss << "south."; + break; + case DIR_E: + ss << "east."; + break; + case DIR_W: + ss << "west."; + break; + case DIR_NE: + ss << "north-east."; + break; + case DIR_NW: + ss << "north-west."; + break; + case DIR_SE: + ss << "south-east."; + break; + case DIR_SW: + ss << "south-west."; + break; + } + } + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + return true; +} + +bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + MonsterType* mType = g_monsters.getMonsterType(param); + if (!mType) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!player->hasFlag(PlayerFlag_CanSummonAll)) { + if (!mType->info.isSummonable) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMana() < mType->info.manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getSummonCount() >= 2) { + player->sendCancelMessage("You cannot summon more creatures."); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Monster* monster = Monster::createMonster(param); + if (!monster) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + // Place the monster + creature->addSummon(monster); + + if (!g_game.placeCreature(monster, creature->getPosition(), true)) { + creature->removeSummon(monster); + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, mType->info.manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const Position& currentPos = creature->getPosition(); + const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); + + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + + if (strcasecmp(param.c_str(), "up") == 0) { + if (currentPos.z != 8) { + Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } else if (strcasecmp(param.c_str(), "down") == 0) { + if (currentPos.z != 7) { + Tile* tmpTile = g_game.map.getTile(destPos); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } + + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + ReturnValue ret = CreateIllusion(creature, param, 180000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool InstantSpell::canCast(const Player* player) const +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return false; + } + + if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { + return true; + } + + if (isLearnable()) { + if (player->hasLearnedInstantSpell(getName())) { + return true; + } + } else { + if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) { + return true; + } + } + + return false; +} + +std::string ConjureSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool ConjureSpell::configureEvent(const pugi::xml_node& node) +{ + if (!InstantSpell::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("conjureId"))) { + conjureId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("conjureCount"))) { + conjureCount = pugi::cast(attr.value()); + } else if (conjureId != 0) { + // load default charges from items.xml + const ItemType& it = Item::items[conjureId]; + if (it.charges != 0) { + conjureCount = it.charges; + } + } + + if ((attr = node.attribute("reagentId"))) { + reagentId = pugi::cast(attr.value()); + } + + ItemType& iType = Item::items.getItemType(conjureId); + if (iType.isRune()) { + iType.runeSpellName = words; + } + + return true; +} + +bool ConjureSpell::loadFunction(const pugi::xml_attribute&) +{ + scripted = false; + return true; +} + +bool ConjureSpell::conjureItem(Creature* creature) const +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const uint32_t conjureCost = getManaCost(player); + const uint32_t soulCost = getSoulCost(); + + if (reagentId != 0) { + bool foundReagent = false; + + Item* item = player->getInventoryItem(CONST_SLOT_LEFT); + if (item && item->getID() == reagentId) { + foundReagent = true; + + // left arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + item = player->getInventoryItem(CONST_SLOT_RIGHT); + if (item && item->getID() == reagentId && player->getMana() >= conjureCost) { + foundReagent = true; + + // right arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + if (!foundReagent) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } else { + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + Spell::postCastSpell(player, conjureCost, soulCost); + } + + postCastSpell(player, true, false); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool ConjureSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (scripted) { + LuaVariant var; + var.type = VARIANT_STRING; + var.text = param; + return executeCastSpell(player, var); + } + return conjureItem(player); +} + +std::string RuneSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool RuneSpell::configureEvent(const pugi::xml_node& node) +{ + if (!Spell::configureSpell(node)) { + return false; + } + + if (!Action::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if (!(attr = node.attribute("id"))) { + std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; + return false; + } + runeId = pugi::cast(attr.value()); + + uint32_t charges; + if ((attr = node.attribute("charges"))) { + charges = pugi::cast(attr.value()); + } else { + charges = 0; + } + + hasCharges = (charges > 0); + + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(runeId); + iType.runeMagLevel = magLevel; + iType.runeLevel = level; + iType.charges = charges; + + return true; +} + +bool RuneSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "chameleon") == 0) { + runeFunction = Illusion; + } else if (strcasecmp(functionName, "convince") == 0) { + runeFunction = Convince; + } else { + std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + +bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo) +{ + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Item* illusionItem = thing->getItem(); + if (!illusionItem || !illusionItem->isMoveable()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t itemId = illusionItem->getID(); + if (illusionItem->isDisguised()) { + itemId = illusionItem->getDisguiseId(); + } + + ReturnValue ret = CreateIllusion(player, itemId, 200000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo) +{ + if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (player->getSummonCount() >= 2) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Creature* convinceCreature = thing->getCreature(); + if (!convinceCreature) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t manaCost = 0; + if (convinceCreature->getMonster()) { + manaCost = convinceCreature->getMonster()->getManaCost(); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!convinceCreature->convinceCreature(player)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos) +{ + if (player->hasFlag(PlayerFlag_CannotUseSpells)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } + + ReturnValue ret = Action::canExecuteAction(player, toPos); + if (ret != RETURNVALUE_NOERROR) { + return ret; + } + + if (toPos.x == 0xFFFF) { + if (needTarget) { + return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES; + } else if (!selfTarget) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + + return RETURNVALUE_NOERROR; +} + +bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition) +{ + if (!playerRuneSpellCheck(player, toPosition)) { + return false; + } + + bool result = false; + if (scripted) { + LuaVariant var; + + if (needTarget) { + var.type = VARIANT_NUMBER; + + if (target == nullptr) { + Tile* toTile = g_game.map.getTile(toPosition); + if (toTile) { + const Creature* visibleCreature = toTile->getTopCreature(); + if (visibleCreature) { + var.number = visibleCreature->getID(); + } + } + } else { + var.number = target->getCreature()->getID(); + } + } else { + var.type = VARIANT_POSITION; + var.pos = toPosition; + } + + result = internalCastSpell(player, var); + } else if (runeFunction) { + result = runeFunction(this, player, toPosition); + } + + if (!result) { + return false; + } + + postCastSpell(player); + if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES)) { + int32_t newCount = std::max(0, item->getCharges() - 1); + g_game.transformItem(item, item->getID(), newCount); + } + return true; +} + +bool RuneSpell::castSpell(Creature* creature) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = creature->getID(); + return internalCastSpell(creature, var); +} + +bool RuneSpell::castSpell(Creature* creature, Creature* target) +{ + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + return internalCastSpell(creature, var); +} + +bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var) +{ + bool result; + if (scripted) { + result = executeCastSpell(creature, var); + } else { + result = false; + } + return result; +} + +bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var) +{ + //onCastSpell(creature, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + + LuaScriptInterface::pushVariant(L, var); + + return scriptInterface->callFunction(2); +} diff --git a/src/spells.h b/src/spells.h new file mode 100644 index 0000000..10a7a35 --- /dev/null +++ b/src/spells.h @@ -0,0 +1,322 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 +#define FS_SPELLS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 + +#include "luascript.h" +#include "player.h" +#include "actions.h" +#include "talkaction.h" +#include "baseevents.h" + +class InstantSpell; +class ConjureSpell; +class RuneSpell; +class Spell; + +typedef std::map VocSpellMap; + +class Spells final : public BaseEvents +{ + public: + Spells(); + ~Spells(); + + // non-copyable + Spells(const Spells&) = delete; + Spells& operator=(const Spells&) = delete; + + Spell* getSpellByName(const std::string& name); + RuneSpell* getRuneSpell(uint32_t id); + RuneSpell* getRuneSpellByName(const std::string& name); + + InstantSpell* getInstantSpell(const std::string& words); + InstantSpell* getInstantSpellByName(const std::string& name); + + uint32_t getInstantSpellCount(const Player* player) const; + InstantSpell* getInstantSpellByIndex(const Player* player, uint32_t index); + + TalkActionResult_t playerSaySpell(Player* player, std::string& words); + + static Position getCasterPosition(Creature* creature, Direction dir); + std::string getScriptBaseName() const final; + + protected: + void clear() final; + LuaScriptInterface& getScriptInterface() final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + + std::map runes; + std::map instants; + + friend class CombatSpell; + LuaScriptInterface scriptInterface { "Spell Interface" }; +}; + +typedef bool (InstantSpellFunction)(const InstantSpell* spell, Creature* creature, const std::string& param); +typedef bool (RuneSpellFunction)(const RuneSpell* spell, Player* player, const Position& posTo); + +class BaseSpell +{ + public: + constexpr BaseSpell() = default; + virtual ~BaseSpell() = default; + + virtual bool castSpell(Creature* creature) = 0; + virtual bool castSpell(Creature* creature, Creature* target) = 0; +}; + +class CombatSpell final : public Event, public BaseSpell +{ + public: + CombatSpell(Combat* combat, bool needTarget, bool needDirection); + ~CombatSpell(); + + // non-copyable + CombatSpell(const CombatSpell&) = delete; + CombatSpell& operator=(const CombatSpell&) = delete; + + bool castSpell(Creature* creature) final; + bool castSpell(Creature* creature, Creature* target) final; + bool configureEvent(const pugi::xml_node&) final { + return true; + } + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool loadScriptCombat(); + Combat* getCombat() { + return combat; + } + + protected: + std::string getScriptEventName() const final { + return "onCastSpell"; + } + + Combat* combat; + + bool needDirection; + bool needTarget; +}; + +class Spell : public BaseSpell +{ + public: + Spell() = default; + + bool configureSpell(const pugi::xml_node& node); + const std::string& getName() const { + return name; + } + + void postCastSpell(Player* player, bool finishedSpell = true, bool payCost = true) const; + static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); + + uint32_t getManaCost(const Player* player) const; + uint32_t getSoulCost() const { + return soul; + } + uint32_t getLevel() const { + return level; + } + uint32_t getMagicLevel() const { + return magLevel; + } + uint32_t getManaPercent() const { + return manaPercent; + } + bool isPremium() const { + return premium; + } + + virtual bool isInstant() const = 0; + bool isLearnable() const { + return learnable; + } + + static ReturnValue CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, const std::string& name, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, uint32_t itemId, int32_t time); + + const VocSpellMap& getVocMap() const { + return vocSpellMap; + } + + protected: + bool playerSpellCheck(Player* player) const; + bool playerInstantSpellCheck(Player* player, const Position& toPos); + bool playerRuneSpellCheck(Player* player, const Position& toPos); + + uint8_t spellId = 0; + + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t soul = 0; + uint32_t cooldown = 0; + uint32_t level = 0; + uint32_t magLevel = 0; + int32_t range = -1; + + bool needTarget = false; + bool needWeapon = false; + bool selfTarget = false; + bool blockingSolid = false; + bool blockingCreature = false; + bool aggressive = true; + bool learnable = false; + bool enabled = true; + bool premium = false; + + VocSpellMap vocSpellMap; + + private: + std::string name; +}; + +class InstantSpell : public TalkAction, public Spell +{ + public: + explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr) override; + + virtual bool playerCastInstant(Player* player, std::string& param); + + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool isInstant() const final { + return true; + } + bool getHasParam() const { + return hasParam; + } + bool getHasPlayerNameParam() const { + return hasPlayerNameParam; + } + bool canCast(const Player* player) const; + bool canThrowSpell(const Creature* creature, const Creature* target) const; + + protected: + std::string getScriptEventName() const override; + + static InstantSpellFunction HouseGuestList; + static InstantSpellFunction HouseSubOwnerList; + static InstantSpellFunction HouseDoorList; + static InstantSpellFunction HouseKick; + static InstantSpellFunction SearchPlayer; + static InstantSpellFunction SummonMonster; + static InstantSpellFunction Levitate; + static InstantSpellFunction Illusion; + + static House* getHouseFromPos(Creature* creature); + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + InstantSpellFunction* function = nullptr; + + bool needDirection = false; + bool hasParam = false; + bool hasPlayerNameParam = false; + bool checkLineOfSight = true; + bool casterTargetOrDirection = false; +}; + +class ConjureSpell final : public InstantSpell +{ + public: + explicit ConjureSpell(LuaScriptInterface* interface) : InstantSpell(interface) { + aggressive = false; + } + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + bool playerCastInstant(Player* player, std::string& param) final; + + bool castSpell(Creature*) final { + return false; + } + bool castSpell(Creature*, Creature*) final { + return false; + } + + protected: + std::string getScriptEventName() const final; + + bool conjureItem(Creature* creature) const; + + uint32_t conjureId = 0; + uint32_t conjureCount = 1; + uint32_t reagentId = 0; +}; + +class RuneSpell final : public Action, public Spell +{ + public: + explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + ReturnValue canExecuteAction(const Player* player, const Position& toPos) final; + bool hasOwnErrorHandler() final { + return true; + } + Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const final { + return targetCreature; + } + + bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition) final; + + bool castSpell(Creature* creature) final; + bool castSpell(Creature* creature, Creature* target) final; + + //scripting + bool executeCastSpell(Creature* creature, const LuaVariant& var); + + bool isInstant() const final { + return false; + } + uint16_t getRuneItemId() const { + return runeId; + } + + protected: + std::string getScriptEventName() const final; + + static RuneSpellFunction Illusion; + static RuneSpellFunction Convince; + + bool internalCastSpell(Creature* creature, const LuaVariant& var); + + RuneSpellFunction* runeFunction = nullptr; + uint16_t runeId = 0; + bool hasCharges = true; +}; + +#endif diff --git a/src/talkaction.cpp b/src/talkaction.cpp new file mode 100644 index 0000000..5474c1a --- /dev/null +++ b/src/talkaction.cpp @@ -0,0 +1,155 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "player.h" +#include "talkaction.h" +#include "pugicast.h" + +TalkActions::TalkActions() + : scriptInterface("TalkAction Interface") +{ + scriptInterface.initState(); +} + +TalkActions::~TalkActions() +{ + clear(); +} + +void TalkActions::clear() +{ + for (TalkAction* talkAction : talkActions) { + delete talkAction; + } + talkActions.clear(); + + scriptInterface.reInitState(); +} + +LuaScriptInterface& TalkActions::getScriptInterface() +{ + return scriptInterface; +} + +std::string TalkActions::getScriptBaseName() const +{ + return "talkactions"; +} + +Event* TalkActions::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "talkaction") != 0) { + return nullptr; + } + return new TalkAction(&scriptInterface); +} + +bool TalkActions::registerEvent(Event* event, const pugi::xml_node&) +{ + talkActions.push_front(static_cast(event)); // event is guaranteed to be a TalkAction + return true; +} + +TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const +{ + size_t wordsLength = words.length(); + for (TalkAction* talkAction : talkActions) { + const std::string& talkactionWords = talkAction->getWords(); + size_t talkactionLength = talkactionWords.length(); + if (wordsLength < talkactionLength || strncasecmp(words.c_str(), talkactionWords.c_str(), talkactionLength) != 0) { + continue; + } + + std::string param; + if (wordsLength != talkactionLength) { + param = words.substr(talkactionLength); + if (param.front() != ' ') { + continue; + } + trim_left(param, ' '); + + char separator = talkAction->getSeparator(); + if (separator != ' ') { + if (!param.empty()) { + if (param.front() != separator) { + continue; + } else { + param.erase(param.begin()); + } + } + } + } + + if (talkAction->executeSay(player, param, type)) { + return TALKACTION_CONTINUE; + } else { + return TALKACTION_BREAK; + } + } + return TALKACTION_CONTINUE; +} + +bool TalkAction::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute wordsAttribute = node.attribute("words"); + if (!wordsAttribute) { + std::cout << "[Error - TalkAction::configureEvent] Missing words for talk action or spell" << std::endl; + return false; + } + + pugi::xml_attribute separatorAttribute = node.attribute("separator"); + if (separatorAttribute) { + separator = pugi::cast(separatorAttribute.value()); + } + + words = wordsAttribute.as_string(); + return true; +} + +std::string TalkAction::getScriptEventName() const +{ + return "onSay"; +} + +bool TalkAction::executeSay(Player* player, const std::string& param, SpeakClasses type) const +{ + //onSay(player, words, param, type) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - TalkAction::executeSay] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(scriptId, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + + scriptInterface->pushFunction(scriptId); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushString(L, words); + LuaScriptInterface::pushString(L, param); + lua_pushnumber(L, type); + + return scriptInterface->callFunction(4); +} diff --git a/src/talkaction.h b/src/talkaction.h new file mode 100644 index 0000000..a9fdd62 --- /dev/null +++ b/src/talkaction.h @@ -0,0 +1,85 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C +#define FS_TALKACTION_H_E6AABAC0F89843469526ADF310F3131C + +#include "luascript.h" +#include "baseevents.h" +#include "const.h" + +enum TalkActionResult_t { + TALKACTION_CONTINUE, + TALKACTION_BREAK, + TALKACTION_FAILED, +}; + +class TalkAction; + +class TalkActions : public BaseEvents +{ + public: + TalkActions(); + ~TalkActions(); + + // non-copyable + TalkActions(const TalkActions&) = delete; + TalkActions& operator=(const TalkActions&) = delete; + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; + + protected: + LuaScriptInterface& getScriptInterface() final; + std::string getScriptBaseName() const final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; + void clear() final; + + // TODO: Store TalkAction objects directly in the list instead of using pointers + std::forward_list talkActions; + + LuaScriptInterface scriptInterface; +}; + +class TalkAction : public Event +{ + public: + explicit TalkAction(LuaScriptInterface* interface) : Event(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + + const std::string& getWords() const { + return words; + } + char getSeparator() const { + return separator; + } + + //scripting + bool executeSay(Player* player, const std::string& param, SpeakClasses type) const; + // + + protected: + std::string getScriptEventName() const override; + + std::string words; + char separator = '"'; +}; + +#endif diff --git a/src/tasks.cpp b/src/tasks.cpp new file mode 100644 index 0000000..3626cf4 --- /dev/null +++ b/src/tasks.cpp @@ -0,0 +1,98 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "tasks.h" +#include "game.h" + +extern Game g_game; + +void Dispatcher::threadMain() +{ + // NOTE: second argument defer_lock is to prevent from immediate locking + std::unique_lock taskLockUnique(taskLock, std::defer_lock); + + while (getState() != THREAD_STATE_TERMINATED) { + // check if there are tasks waiting + taskLockUnique.lock(); + + if (taskList.empty()) { + //if the list is empty wait for signal + taskSignal.wait(taskLockUnique); + } + + if (!taskList.empty()) { + // take the first task + Task* task = taskList.front(); + taskList.pop_front(); + taskLockUnique.unlock(); + + if (!task->hasExpired()) { + ++dispatcherCycle; + // execute it + (*task)(); + + g_game.map.clearSpectatorCache(); + } + delete task; + } else { + taskLockUnique.unlock(); + } + } +} + +void Dispatcher::addTask(Task* task, bool push_front /*= false*/) +{ + bool do_signal = false; + + taskLock.lock(); + + if (getState() == THREAD_STATE_RUNNING) { + do_signal = taskList.empty(); + + if (push_front) { + taskList.push_front(task); + } else { + taskList.push_back(task); + } + } else { + delete task; + } + + taskLock.unlock(); + + // send a signal if the list was empty + if (do_signal) { + taskSignal.notify_one(); + } +} + +void Dispatcher::shutdown() +{ + Task* task = createTask([this]() { + setState(THREAD_STATE_TERMINATED); + taskSignal.notify_one(); + }); + + std::lock_guard lockClass(taskLock); + taskList.push_back(task); + + taskSignal.notify_one(); +} diff --git a/src/tasks.h b/src/tasks.h new file mode 100644 index 0000000..ad08653 --- /dev/null +++ b/src/tasks.h @@ -0,0 +1,95 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 +#define FS_TASKS_H_A66AC384766041E59DCA059DAB6E1976 + +#include +#include "thread_holder_base.h" +#include "enums.h" + +const int DISPATCHER_TASK_EXPIRATION = 2000; +const auto SYSTEM_TIME_ZERO = std::chrono::system_clock::time_point(std::chrono::milliseconds(0)); + +class Task +{ + public: + // DO NOT allocate this class on the stack + explicit Task(std::function f) : func(std::move(f)) {} + Task(uint32_t ms, std::function f) : + expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) {} + + virtual ~Task() = default; + void operator()() { + func(); + } + + void setDontExpire() { + expiration = SYSTEM_TIME_ZERO; + } + + bool hasExpired() const { + if (expiration == SYSTEM_TIME_ZERO) { + return false; + } + return expiration < std::chrono::system_clock::now(); + } + + protected: + // Expiration has another meaning for scheduler tasks, + // then it is the time the task should be added to the + // dispatcher + std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; + std::function func; +}; + +inline Task* createTask(const std::function& f) +{ + return new Task(f); +} + +inline Task* createTask(uint32_t expiration, const std::function& f) +{ + return new Task(expiration, f); +} + +class Dispatcher : public ThreadHolder { + public: + void addTask(Task* task, bool push_front = false); + + void shutdown(); + + uint64_t getDispatcherCycle() const { + return dispatcherCycle; + } + + void threadMain(); + + protected: + std::thread thread; + std::mutex taskLock; + std::condition_variable taskSignal; + + std::list taskList; + uint64_t dispatcherCycle = 0; +}; + +extern Dispatcher g_dispatcher; + +#endif diff --git a/src/teleport.cpp b/src/teleport.cpp new file mode 100644 index 0000000..92a2a23 --- /dev/null +++ b/src/teleport.cpp @@ -0,0 +1,122 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "teleport.h" +#include "game.h" + +extern Game g_game; + +Attr_ReadValue Teleport::readAttr(AttrTypes_t attr, PropStream& propStream) +{ + if (attr == ATTR_TELE_DEST) { + if (!propStream.read(destPos.x) || !propStream.read(destPos.y) || !propStream.read(destPos.z)) { + return ATTR_READ_ERROR; + } + return ATTR_READ_CONTINUE; + } + return Item::readAttr(attr, propStream); +} + +void Teleport::serializeAttr(PropWriteStream& propWriteStream) const +{ + Item::serializeAttr(propWriteStream); + + propWriteStream.write(ATTR_TELE_DEST); + propWriteStream.write(destPos.x); + propWriteStream.write(destPos.y); + propWriteStream.write(destPos.z); +} + +ReturnValue Teleport::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Teleport::queryMaxCount(int32_t, const Thing&, uint32_t, uint32_t&, uint32_t) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +ReturnValue Teleport::queryRemove(const Thing&, uint32_t, uint32_t) const +{ + return RETURNVALUE_NOERROR; +} + +Cylinder* Teleport::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) +{ + return this; +} + +void Teleport::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void Teleport::addThing(int32_t, Thing* thing) +{ + Tile* destTile = g_game.map.getTile(destPos); + if (!destTile) { + return; + } + + const MagicEffectClasses effect = Item::items[id].magicEffect; + + if (Creature* creature = thing->getCreature()) { + Position origPos = creature->getPosition(); + g_game.internalCreatureTurn(creature, origPos.x > destPos.x ? DIRECTION_WEST : DIRECTION_EAST); + g_game.map.moveCreature(*creature, *destTile); + if (effect != CONST_ME_NONE) { + g_game.addMagicEffect(origPos, effect); + g_game.addMagicEffect(destTile->getPosition(), effect); + } + } else if (Item* item = thing->getItem()) { + if (effect != CONST_ME_NONE) { + g_game.addMagicEffect(destTile->getPosition(), effect); + g_game.addMagicEffect(item->getPosition(), effect); + } + g_game.internalMoveItem(getTile(), destTile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr); + } +} + +void Teleport::updateThing(Thing*, uint16_t, uint32_t) +{ + // +} + +void Teleport::replaceThing(uint32_t, Thing*) +{ + // +} + +void Teleport::removeThing(Thing*, uint32_t) +{ + // +} + +void Teleport::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void Teleport::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); +} diff --git a/src/teleport.h b/src/teleport.h new file mode 100644 index 0000000..fd6cd2e --- /dev/null +++ b/src/teleport.h @@ -0,0 +1,72 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC +#define FS_TELEPORT_H_873B7F7F1DB24101A7ACFB54B25E0ABC + +#include "tile.h" + +class Teleport final : public Item, public Cylinder +{ + public: + explicit Teleport(uint16_t type) : Item(type) {}; + + Teleport* getTeleport() final { + return this; + } + const Teleport* getTeleport() const final { + return this; + } + + //serialization + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; + + const Position& getDestPos() const { + return destPos; + } + void setDestPos(Position pos) { + destPos = std::move(pos); + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const final; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) final; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + private: + Position destPos; +}; + +#endif diff --git a/src/thing.cpp b/src/thing.cpp new file mode 100644 index 0000000..c053b29 --- /dev/null +++ b/src/thing.cpp @@ -0,0 +1,42 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "thing.h" +#include "tile.h" + +const Position& Thing::getPosition() const +{ + const Tile* tile = getTile(); + if (!tile) { + return Tile::nullptr_tile.getPosition(); + } + return tile->getPosition(); +} + +Tile* Thing::getTile() +{ + return dynamic_cast(this); +} + +const Tile* Thing::getTile() const +{ + return dynamic_cast(this); +} diff --git a/src/thing.h b/src/thing.h new file mode 100644 index 0000000..ee9304e --- /dev/null +++ b/src/thing.h @@ -0,0 +1,86 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 +#define FS_THING_H_6F16A8E566AF4ACEAE02CF32A7246144 + +#include "position.h" + +class Tile; +class Cylinder; +class Item; +class Creature; +class Container; + +class Thing +{ + protected: + constexpr Thing() = default; + ~Thing() = default; + + public: + // non-copyable + Thing(const Thing&) = delete; + Thing& operator=(const Thing&) = delete; + + virtual std::string getDescription(int32_t lookDistance) const = 0; + + virtual Cylinder* getParent() const { + return nullptr; + } + virtual Cylinder* getRealParent() const { + return getParent(); + } + + virtual void setParent(Cylinder*) { + // + } + + virtual Tile* getTile(); + virtual const Tile* getTile() const; + + virtual const Position& getPosition() const; + virtual int32_t getThrowRange() const = 0; + virtual bool isPushable() const = 0; + + virtual Container* getContainer() { + return nullptr; + } + virtual const Container* getContainer() const { + return nullptr; + } + virtual Item* getItem() { + return nullptr; + } + virtual const Item* getItem() const { + return nullptr; + } + virtual Creature* getCreature() { + return nullptr; + } + virtual const Creature* getCreature() const { + return nullptr; + } + + virtual bool isRemoved() const { + return true; + } +}; + +#endif diff --git a/src/thread_holder_base.h b/src/thread_holder_base.h new file mode 100644 index 0000000..97dd31d --- /dev/null +++ b/src/thread_holder_base.h @@ -0,0 +1,59 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 +#define FS_THREAD_HOLDER_H_BEB56FC46748E71D15A5BF0773ED2E67 + +#include +#include +#include "enums.h" + +template +class ThreadHolder +{ + public: + ThreadHolder() {} + void start() { + setState(THREAD_STATE_RUNNING); + thread = std::thread(&Derived::threadMain, static_cast(this)); + } + + void stop() { + setState(THREAD_STATE_CLOSING); + } + + void join() { + if (thread.joinable()) { + thread.join(); + } + } + protected: + void setState(ThreadState newState) { + threadState.store(newState, std::memory_order_relaxed); + } + + ThreadState getState() const { + return threadState.load(std::memory_order_relaxed); + } + private: + std::atomic threadState{THREAD_STATE_TERMINATED}; + std::thread thread; +}; + +#endif diff --git a/src/tile.cpp b/src/tile.cpp new file mode 100644 index 0000000..14da480 --- /dev/null +++ b/src/tile.cpp @@ -0,0 +1,1480 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "tile.h" + +#include "creature.h" +#include "combat.h" +#include "game.h" +#include "mailbox.h" +#include "monster.h" +#include "movement.h" +#include "teleport.h" + +extern Game g_game; +extern MoveEvents* g_moveEvents; + +StaticTile real_nullptr_tile(0xFFFF, 0xFFFF, 0xFF); +Tile& Tile::nullptr_tile = real_nullptr_tile; + +bool Tile::hasProperty(ITEMPROPERTY prop) const +{ + if (ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item->hasProperty(prop)) { + return true; + } + } + } + return false; +} + +bool Tile::hasProperty(const Item* exclude, ITEMPROPERTY prop) const +{ + assert(exclude); + + if (ground && exclude != ground && ground->hasProperty(prop)) { + return true; + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item != exclude && item->hasProperty(prop)) { + return true; + } + } + } + + return false; +} + +bool Tile::hasHeight(uint32_t n) const +{ + uint32_t height = 0; + + if (ground) { + if (ground->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + + if (const TileItemVector* items = getItemList()) { + for (const Item* item : *items) { + if (item->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + + if (n == height) { + return true; + } + } + } + return false; +} + +size_t Tile::getCreatureCount() const +{ + if (const CreatureVector* creatures = getCreatures()) { + return creatures->size(); + } + return 0; +} + +size_t Tile::getItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->size(); + } + return 0; +} + +uint32_t Tile::getTopItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopItemCount(); + } + return 0; +} + +uint32_t Tile::getDownItemCount() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getDownItemCount(); + } + return 0; +} + +std::string Tile::getDescription(int32_t) const +{ + return "You dont know why, but you cant see anything!"; +} + +Teleport* Tile::getTeleportItem() const +{ + if (!hasFlag(TILESTATE_TELEPORT)) { + return nullptr; + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getTeleport()) { + return (*it)->getTeleport(); + } + } + } + return nullptr; +} + +MagicField* Tile::getFieldItem() const +{ + if (!hasFlag(TILESTATE_MAGICFIELD)) { + return nullptr; + } + + if (ground && ground->getMagicField()) { + return ground->getMagicField(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getMagicField()) { + return (*it)->getMagicField(); + } + } + } + return nullptr; +} + +Mailbox* Tile::getMailbox() const +{ + if (!hasFlag(TILESTATE_MAILBOX)) { + return nullptr; + } + + if (ground && ground->getMailbox()) { + return ground->getMailbox(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getMailbox()) { + return (*it)->getMailbox(); + } + } + } + return nullptr; +} + +BedItem* Tile::getBedItem() const +{ + if (!hasFlag(TILESTATE_BED)) { + return nullptr; + } + + if (ground && ground->getBed()) { + return ground->getBed(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getBed()) { + return (*it)->getBed(); + } + } + } + return nullptr; +} + +Creature* Tile::getTopCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->begin(); + } + } + return nullptr; +} + +const Creature* Tile::getBottomCreature() const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (!creatures->empty()) { + return *creatures->rbegin(); + } + } + return nullptr; +} + +Creature* Tile::getTopVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + if (player && player->isAccessPlayer()) { + return getTopCreature(); + } + + for (Creature* tileCreature : *creatures) { + if (creature->canSeeCreature(tileCreature)) { + return tileCreature; + } + } + } else { + for (Creature* tileCreature : *creatures) { + if (!tileCreature->isInvisible()) { + const Player* player = tileCreature->getPlayer(); + if (!player || !player->isInGhostMode()) { + return tileCreature; + } + } + } + } + } + return nullptr; +} + +const Creature* Tile::getBottomVisibleCreature(const Creature* creature) const +{ + if (const CreatureVector* creatures = getCreatures()) { + if (creature) { + const Player* player = creature->getPlayer(); + if (player && player->isAccessPlayer()) { + return getBottomCreature(); + } + + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (creature->canSeeCreature(*it)) { + return *it; + } + } + } else { + for (auto it = creatures->rbegin(), end = creatures->rend(); it != end; ++it) { + if (!(*it)->isInvisible()) { + const Player* player = (*it)->getPlayer(); + if (!player || !player->isInGhostMode()) { + return *it; + } + } + } + } + } + return nullptr; +} + +Item* Tile::getTopDownItem() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopDownItem(); + } + return nullptr; +} + +Item* Tile::getTopTopItem() const +{ + if (const TileItemVector* items = getItemList()) { + return items->getTopTopItem(); + } + return nullptr; +} + +Item* Tile::getItemByTopOrder(int32_t topOrder) +{ + //topOrder: + //1: borders + //2: ladders, signs, splashes + //3: doors etc + //4: creatures + if (TileItemVector* items = getItemList()) { + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder == topOrder) { + return (*it); + } + } + } + return nullptr; +} + +Thing* Tile::getTopVisibleThing(const Creature* creature) +{ + Thing* thing = getTopVisibleCreature(creature); + if (thing) { + return thing; + } + + TileItemVector* items = getItemList(); + if (items) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + return (*it); + } + + for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { + return (*it); + } + } + + return ground; +} + +void Tile::onAddTileItem(Item* item) +{ + setTileFlags(item); + + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); + + //send to client + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onAddTileItem(this, cylinderMapPos); + } +} + +void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType) +{ + const Position& cylinderMapPos = getPosition(); + + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); + + //send to client + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); + } +} + +void Tile::onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item) +{ + resetTileFlags(item); + + const Position& cylinderMapPos = getPosition(); + const ItemType& iType = Item::items[item->getID()]; + + //send to client + size_t i = 0; + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendRemoveTileThing(cylinderMapPos, oldStackPosVector[i++]); + } + } + + //event methods + for (Creature* spectator : list) { + spectator->onRemoveTileItem(this, cylinderMapPos, iType, item); + } +} + +void Tile::onUpdateTile(const SpectatorVec& list) +{ + const Position& cylinderMapPos = getPosition(); + + //send to clients + for (Creature* spectator : list) { + spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos); + } +} + +ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags, Creature*) const +{ + if (const Creature* creature = thing.getCreature()) { + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RETURNVALUE_NOERROR; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (ground == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (const Monster* monster = creature->getMonster()) { + if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_TELEPORT)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKPATH | TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PLACECHECK, flags) && hasFlag(TILESTATE_BLOCKSOLID)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const CreatureVector* creatures = getCreatures(); + if (monster->canPushCreatures() && !monster->isSummon()) { + if (creatures) { + for (Creature* tileCreature : *creatures) { + if (tileCreature->getPlayer() && tileCreature->getPlayer()->isInGhostMode()) { + continue; + } + + const Monster* creatureMonster = tileCreature->getMonster(); + if (!creatureMonster || !tileCreature->isPushable() || + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + } + } else if (creatures && !creatures->empty()) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_BLOCKSOLID) || (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_NOFIELDBLOCKPATH))) { + if (!(monster->canPushItems() || hasBitSet(FLAG_IGNOREBLOCKITEM, flags))) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && + !hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { + if (hasFlag(TILESTATE_FIREDAMAGE) && !monster->isImmune(COMBAT_FIREDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_POISONDAMAGE) && !monster->isImmune(COMBAT_EARTHDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_ENERGYDAMAGE) && !monster->isImmune(COMBAT_ENERGYDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + return RETURNVALUE_NOERROR; + } + + const CreatureVector* creatures = getCreatures(); + if (const Player* player = creature->getPlayer()) { + if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_BLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) { + //player is trying to login to a "no logout" tile + return RETURNVALUE_NOTPOSSIBLE; + } + + const Tile* playerTile = player->getTile(); + if (playerTile && player->isPzLocked()) { + if (!playerTile->hasFlag(TILESTATE_PVPZONE)) { + //player is trying to enter a pvp zone while being pz-locked + if (hasFlag(TILESTATE_PVPZONE)) { + return RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE; + } + } else if (!hasFlag(TILESTATE_PVPZONE)) { + // player is trying to leave a pvp zone while being pz-locked + return RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE; + } + + if ((!playerTile->hasFlag(TILESTATE_NOPVPZONE) && hasFlag(TILESTATE_NOPVPZONE)) || + (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && hasFlag(TILESTATE_PROTECTIONZONE))) { + // player is trying to enter a non-pvp/protection zone while being pz-locked + return RETURNVALUE_PLAYERISPZLOCKED; + } + } + } else if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (!hasBitSet(FLAG_IGNOREBLOCKITEM, flags)) { + //If the FLAG_IGNOREBLOCKITEM bit isn't set we dont have to iterate every single item + if (hasFlag(TILESTATE_BLOCKSOLID)) { + return RETURNVALUE_NOTENOUGHROOM; + } + } else { + //FLAG_IGNOREBLOCKITEM is set + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + if (iiType.blockSolid) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + if (const auto items = getItemList()) { + for (const Item* item : *items) { + const ItemType& iiType = Item::items[item->getID()]; + if (iiType.blockSolid && !iiType.moveable) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + } + } + } else if (const Item* item = thing.getItem()) { + const TileItemVector* items = getItemList(); + if (items && items->size() >= 0xFFFF) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_NOLIMIT, flags)) { + return RETURNVALUE_NOERROR; + } + + bool itemIsHangable = item->isHangable(); + if (ground == nullptr && !itemIsHangable) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const CreatureVector* creatures = getCreatures(); + if (creatures && !creatures->empty() && item->isBlocking() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags)) { + for (const Creature* tileCreature : *creatures) { + if (!tileCreature->isInGhostMode()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + + if (item->isMagicField() && hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + if (items) { + for (const Item* tileItem : *items) { + if (tileItem->isHangable()) { + return RETURNVALUE_NEEDEXCHANGE; + } + } + } + } else { + if (ground) { + const ItemType& iiType = Item::items[ground->getID()]; + if (iiType.blockSolid) { + if (!iiType.allowPickupable || item->isMagicField() || item->isBlocking()) { + if (!item->isPickupable()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + } + + if (items) { + for (const Item* tileItem : *items) { + const ItemType& iiType = Item::items[tileItem->getID()]; + if (!iiType.blockSolid) { + continue; + } + + if (iiType.allowPickupable && !item->isMagicField() && !item->isBlocking()) { + continue; + } + + if (!item->isPickupable()) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (!iiType.hasHeight || iiType.pickupable || iiType.isBed()) { + return RETURNVALUE_NOTENOUGHROOM; + } + } + } + } + } + return RETURNVALUE_NOERROR; +} + +ReturnValue Tile::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const +{ + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; +} + +ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const +{ + int32_t index = getThingIndex(&thing); + if (index == -1) { + return RETURNVALUE_NOTPOSSIBLE; + } + + const Item* item = thing.getItem(); + if (item == nullptr) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (count == 0 || (item->isStackable() && count > item->getItemCount())) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) { + return RETURNVALUE_NOTMOVEABLE; + } + + return RETURNVALUE_NOERROR; +} + +Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t&) +{ + Thing* destThing = getTopDownItem(); + if (destThing) { + *destItem = destThing->getItem(); + } + + return this; +} + +void Tile::addThing(Thing* thing) +{ + addThing(0, thing); +} + +void Tile::addThing(int32_t, Thing* thing) +{ + Creature* creature = thing->getCreature(); + if (creature) { + g_game.map.clearSpectatorCache(); + creature->setParent(this); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->end(), creature); + } else { + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + TileItemVector* items = getItemList(); + if (items && items->size() >= 0xFFFF) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + item->setParent(this); + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.isGroundTile()) { + if (ground == nullptr) { + ground = item; + onAddTileItem(item); + } else { + const ItemType& oldType = Item::items[ground->getID()]; + + Item* oldGround = ground; + ground->setParent(nullptr); + g_game.ReleaseItem(ground); + ground = item; + resetTileFlags(oldGround); + setTileFlags(item); + onUpdateTileItem(oldGround, oldType, item, itemType); + postRemoveNotification(oldGround, nullptr, 0); + } + } else if (itemType.alwaysOnTop) { + if (itemType.isSplash() && items) { + //remove old splash if exists + for (ItemVector::const_iterator it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + Item* oldSplash = *it; + if (!Item::items[oldSplash->getID()].isSplash()) { + continue; + } + + removeThing(oldSplash, 1); + oldSplash->setParent(nullptr); + g_game.ReleaseItem(oldSplash); + postRemoveNotification(oldSplash, nullptr, 0); + break; + } + } + + bool isInserted = false; + + if (items) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + //Note: this is different from internalAddThing + if (itemType.alwaysOnTopOrder <= Item::items[(*it)->getID()].alwaysOnTopOrder) { + items->insert(it, item); + isInserted = true; + break; + } + } + } else { + items = makeItemList(); + } + + if (!isInserted) { + items->push_back(item); + } + + onAddTileItem(item); + } else { + if (itemType.isMagicField()) { + //remove old field item if exists + if (items) { + for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + MagicField* oldField = (*it)->getMagicField(); + if (oldField) { + if (oldField->isReplaceable()) { + removeThing(oldField, 1); + + oldField->setParent(nullptr); + g_game.ReleaseItem(oldField); + postRemoveNotification(oldField, nullptr, 0); + break; + } else { + //This magic field cannot be replaced. + item->setParent(nullptr); + g_game.ReleaseItem(item); + return; + } + } + } + } + } + + items = makeItemList(); + items->insert(items->getBeginDownItem(), item); + items->addDownItemCount(1); + onAddTileItem(item); + } + } +} + +void Tile::updateThing(Thing* thing, uint16_t itemId, uint32_t count) +{ + int32_t index = getThingIndex(thing); + if (index == -1) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + const ItemType& oldType = Item::items[item->getID()]; + const ItemType& newType = Item::items[itemId]; + resetTileFlags(item); + item->setID(itemId); + item->setSubType(count); + setTileFlags(item); + onUpdateTileItem(item, oldType, item, newType); +} + +void Tile::replaceThing(uint32_t index, Thing* thing) +{ + int32_t pos = index; + + Item* item = thing->getItem(); + if (item == nullptr) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + Item* oldItem = nullptr; + bool isInserted = false; + + if (ground) { + if (pos == 0) { + oldItem = ground; + ground = item; + isInserted = true; + } + + --pos; + } + + TileItemVector* items = getItemList(); + if (items && !isInserted) { + int32_t topItemSize = getTopItemCount(); + if (pos < topItemSize) { + auto it = items->getBeginTopItem(); + it += pos; + + oldItem = (*it); + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + + pos -= topItemSize; + } + + CreatureVector* creatures = getCreatures(); + if (creatures) { + if (!isInserted && pos < static_cast(creatures->size())) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + pos -= static_cast(creatures->size()); + } + + if (items && !isInserted) { + int32_t downItemSize = getDownItemCount(); + if (pos < downItemSize) { + auto it = items->getBeginDownItem() + pos; + oldItem = *it; + it = items->erase(it); + items->insert(it, item); + isInserted = true; + } + } + + if (isInserted) { + item->setParent(this); + + resetTileFlags(oldItem); + setTileFlags(item); + const ItemType& oldType = Item::items[oldItem->getID()]; + const ItemType& newType = Item::items[item->getID()]; + onUpdateTileItem(oldItem, oldType, item, newType); + + oldItem->setParent(nullptr); + return /*RETURNVALUE_NOERROR*/; + } +} + +void Tile::removeThing(Thing* thing, uint32_t count) +{ + Creature* creature = thing->getCreature(); + if (creature) { + CreatureVector* creatures = getCreatures(); + if (creatures) { + auto it = std::find(creatures->begin(), creatures->end(), thing); + if (it != creatures->end()) { + g_game.map.clearSpectatorCache(); + creatures->erase(it); + } + } + return; + } + + Item* item = thing->getItem(); + if (!item) { + return; + } + + int32_t index = getThingIndex(item); + if (index == -1) { + return; + } + + if (item == ground) { + ground->setParent(nullptr); + ground = nullptr; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + onRemoveTileItem(list, std::vector(list.size(), 0), item); + return; + } + + TileItemVector* items = getItemList(); + if (!items) { + return; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.alwaysOnTop) { + auto it = std::find(items->getBeginTopItem(), items->getEndTopItem(), item); + if (it == items->getEndTopItem()) { + return; + } + + std::vector oldStackPosVector; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); + } + } + + item->setParent(nullptr); + items->erase(it); + onRemoveTileItem(list, oldStackPosVector, item); + } else { + auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item); + if (it == items->getEndDownItem()) { + return; + } + + if (itemType.stackable && count != item->getItemCount()) { + uint8_t newCount = static_cast(std::max(0, static_cast(item->getItemCount() - count))); + item->setItemCount(newCount); + onUpdateTileItem(item, itemType, item, itemType); + } else { + std::vector oldStackPosVector; + + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); + } + } + + item->setParent(nullptr); + items->erase(it); + items->addDownItemCount(-1); + onRemoveTileItem(list, oldStackPosVector, item); + } + } +} + +void Tile::removeCreature(Creature* creature) +{ + g_game.map.getQTNode(tilePos.x, tilePos.y)->removeCreature(creature); + removeThing(creature, 0); +} + +int32_t Tile::getThingIndex(const Thing* thing) const +{ + int32_t n = -1; + if (ground) { + if (ground == thing) { + return 0; + } + ++n; + } + + const TileItemVector* items = getItemList(); + if (items) { + const Item* item = thing->getItem(); + if (item && item->isAlwaysOnTop()) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + ++n; + if (*it == item) { + return n; + } + } + } else { + n += items->getTopItemCount(); + } + } + + if (const CreatureVector* creatures = getCreatures()) { + if (thing->getCreature()) { + for (Creature* creature : *creatures) { + ++n; + if (creature == thing) { + return n; + } + } + } else { + n += creatures->size(); + } + } + + if (items) { + const Item* item = thing->getItem(); + if (item && !item->isAlwaysOnTop()) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + ++n; + if (*it == item) { + return n; + } + } + } + } + return -1; +} + +int32_t Tile::getClientIndexOfCreature(const Player* player, const Creature* creature) const +{ + int32_t n; + if (ground) { + n = 1; + } else { + n = 0; + } + + const TileItemVector* items = getItemList(); + if (items) { + n += items->getTopItemCount(); + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* c : boost::adaptors::reverse(*creatures)) { + if (c == creature) { + return n; + } else if (player->canSeeCreature(c)) { + ++n; + } + } + } + return -1; +} + +int32_t Tile::getStackposOfCreature(const Player* player, const Creature* creature) const +{ + int32_t n; + if (ground) { + n = 1; + } else { + n = 0; + } + + const TileItemVector* items = getItemList(); + if (items) { + n += items->getTopItemCount(); + if (n >= 10) { + return -1; + } + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* c : boost::adaptors::reverse(*creatures)) { + if (c == creature) { + return n; + } else if (player->canSeeCreature(c)) { + if (++n >= 10) { + return -1; + } + } + } + } + return -1; +} + +int32_t Tile::getStackposOfItem(const Player* player, const Item* item) const +{ + int32_t n = 0; + if (ground) { + if (ground == item) { + return n; + } + ++n; + } + + const TileItemVector* items = getItemList(); + if (items) { + if (item->isAlwaysOnTop()) { + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + if (*it == item) { + return n; + } else if (++n == 10) { + return -1; + } + } + } else { + n += items->getTopItemCount(); + if (n >= 10) { + return -1; + } + } + } + + if (const CreatureVector* creatures = getCreatures()) { + for (const Creature* creature : *creatures) { + if (player->canSeeCreature(creature)) { + if (++n >= 10) { + return -1; + } + } + } + } + + if (items && !item->isAlwaysOnTop()) { + for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { + if (*it == item) { + return n; + } else if (++n >= 10) { + return -1; + } + } + } + return -1; +} + +size_t Tile::getFirstIndex() const +{ + return 0; +} + +size_t Tile::getLastIndex() const +{ + return getThingCount(); +} + +uint32_t Tile::getItemTypeCount(uint16_t itemId, int32_t subType /*= -1*/) const +{ + uint32_t count = 0; + if (ground && ground->getID() == itemId) { + count += Item::countByType(ground, subType); + } + + const TileItemVector* items = getItemList(); + if (items) { + for (const Item* item : *items) { + if (item->getID() == itemId) { + count += Item::countByType(item, subType); + } + } + } + return count; +} + +Thing* Tile::getThing(size_t index) const +{ + if (ground) { + if (index == 0) { + return ground; + } + + --index; + } + + const TileItemVector* items = getItemList(); + if (items) { + uint32_t topItemSize = items->getTopItemCount(); + if (index < topItemSize) { + return items->at(items->getDownItemCount() + index); + } + index -= topItemSize; + } + + if (const CreatureVector* creatures = getCreatures()) { + if (index < creatures->size()) { + return (*creatures)[index]; + } + index -= creatures->size(); + } + + if (items && index < items->getDownItemCount()) { + return items->at(index); + } + return nullptr; +} + +void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); + for (Creature* spectator : list) { + spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); + } + + //add a reference to this item, it may be deleted after being added (mailbox for example) + Creature* creature = thing->getCreature(); + Item* item; + if (creature) { + creature->incrementReferenceCounter(); + item = nullptr; + } else { + item = thing->getItem(); + if (item) { + item->incrementReferenceCounter(); + } + } + + if (link == LINK_OWNER) { + if (hasFlag(TILESTATE_TELEPORT)) { + Teleport* teleport = getTeleportItem(); + if (teleport) { + teleport->addThing(thing); + } + } else if (hasFlag(TILESTATE_MAILBOX)) { + Mailbox* mailbox = getMailbox(); + if (mailbox) { + mailbox->addThing(thing); + } + } + + //calling movement scripts + if (creature) { + g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_IN); + } else if (item) { + g_moveEvents->onItemMove(item, this, true); + } + } + + //release the reference to this item onces we are finished + if (creature) { + g_game.ReleaseCreature(creature); + } else if (item) { + g_game.ReleaseItem(item); + } +} + +void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); + + if (getThingCount() > 8) { + onUpdateTile(list); + } + + for (Creature* spectator : list) { + spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); + } + + //calling movement scripts + Creature* creature = thing->getCreature(); + if (creature) { + g_moveEvents->onCreatureMove(creature, this, MOVE_EVENT_STEP_OUT); + } else { + Item* item = thing->getItem(); + if (item) { + g_moveEvents->onItemMove(item, this, false); + } + } +} + +void Tile::internalAddThing(Thing* thing) +{ + internalAddThing(0, thing); +} + +void Tile::internalAddThing(uint32_t, Thing* thing) +{ + thing->setParent(this); + + Creature* creature = thing->getCreature(); + if (creature) { + g_game.map.clearSpectatorCache(); + CreatureVector* creatures = makeCreatures(); + creatures->insert(creatures->end(), creature); + } else { + Item* item = thing->getItem(); + if (item == nullptr) { + return; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.isGroundTile()) { + if (ground == nullptr) { + ground = item; + setTileFlags(item); + } + return; + } + + TileItemVector* items = makeItemList(); + if (items->size() >= 0xFFFF) { + return /*RETURNVALUE_NOTPOSSIBLE*/; + } + + if (itemType.alwaysOnTop) { + bool isInserted = false; + for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) { + if (Item::items[(*it)->getID()].alwaysOnTopOrder > itemType.alwaysOnTopOrder) { + items->insert(it, item); + isInserted = true; + break; + } + } + + if (!isInserted) { + items->push_back(item); + } + } else { + items->insert(items->getBeginDownItem(), item); + items->addDownItemCount(1); + } + + setTileFlags(item); + } +} + +void Tile::setTileFlags(const Item* item) +{ + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { + setFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_BLOCKPATH)) { + setFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH)) { + setFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + setFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + setFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + setFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + setFlag(TILESTATE_MAILBOX); + } + + if (item->hasProperty(CONST_PROP_BLOCKSOLID)) { + setFlag(TILESTATE_BLOCKSOLID); + } + + if (item->getBed()) { + setFlag(TILESTATE_BED); + } + + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + setFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + setFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + setFlag(TILESTATE_POISONDAMAGE); + } + + const Container* container = item->getContainer(); + if (container && container->getDepotLocker()) { + setFlag(TILESTATE_DEPOT); + } + + if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) { + setFlag(TILESTATE_SUPPORTS_HANGABLE); + } +} + +void Tile::resetTileFlags(const Item* item) +{ + if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) { + resetFlag(TILESTATE_BLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKSOLID)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKSOLID); + } + + if (item->hasProperty(CONST_PROP_BLOCKPATH) && !hasProperty(item, CONST_PROP_BLOCKPATH)) { + resetFlag(TILESTATE_BLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_NOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_NOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_NOFIELDBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLEBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLEBLOCKPATH); + } + + if (item->hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH) && !hasProperty(item, CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + resetFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH); + } + + if (item->getTeleport()) { + resetFlag(TILESTATE_TELEPORT); + } + + if (item->getMagicField()) { + resetFlag(TILESTATE_MAGICFIELD); + } + + if (item->getMailbox()) { + resetFlag(TILESTATE_MAILBOX); + } + + if (item->getBed()) { + resetFlag(TILESTATE_BED); + } + + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + resetFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + resetFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + resetFlag(TILESTATE_POISONDAMAGE); + } + + const Container* container = item->getContainer(); + if (container && container->getDepotLocker()) { + resetFlag(TILESTATE_DEPOT); + } + + if (item->hasProperty(CONST_PROP_SUPPORTHANGABLE)) { + resetFlag(TILESTATE_SUPPORTS_HANGABLE); + } +} + +bool Tile::isMoveableBlocking() const +{ + return !ground || hasFlag(TILESTATE_BLOCKSOLID); +} + +Item* Tile::getUseItem() const +{ + const TileItemVector* items = getItemList(); + if (!items || items->size() == 0) { + return ground; + } + + for (Item* item : boost::adaptors::reverse(*items)) { + if (Item::items[item->getID()].forceUse) { + return item; + } + } + + Item* item = items->getTopDownItem(); + if (!item) { + item = items->getTopTopItem(); + } + return item; +} diff --git a/src/tile.h b/src/tile.h new file mode 100644 index 0000000..0f7f2a9 --- /dev/null +++ b/src/tile.h @@ -0,0 +1,392 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 +#define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 + +#include + +#include "cylinder.h" +#include "item.h" +#include "tools.h" + +class Creature; +class Teleport; +class Mailbox; +class MagicField; +class QTreeLeafNode; +class BedItem; + +typedef std::vector CreatureVector; +typedef std::vector ItemVector; +typedef std::unordered_set SpectatorVec; + +enum tileflags_t : uint32_t { + TILESTATE_NONE = 0, + + TILESTATE_PROTECTIONZONE = 1 << 0, + TILESTATE_NOPVPZONE = 1 << 1, + TILESTATE_NOLOGOUT = 1 << 2, + TILESTATE_PVPZONE = 1 << 3, + TILESTATE_REFRESH = 1 << 4, + TILESTATE_TELEPORT = 1 << 5, + TILESTATE_MAGICFIELD = 1 << 6, + TILESTATE_MAILBOX = 1 << 7, + TILESTATE_BED = 1 << 8, + TILESTATE_DEPOT = 1 << 9, + TILESTATE_BLOCKSOLID = 1 << 10, + TILESTATE_BLOCKPATH = 1 << 11, + TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 12, + TILESTATE_IMMOVABLEBLOCKPATH = 1 << 13, + TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 14, + TILESTATE_NOFIELDBLOCKPATH = 1 << 15, + TILESTATE_SUPPORTS_HANGABLE = 1 << 16, + TILESTATE_FIREDAMAGE = 1 << 17, + TILESTATE_POISONDAMAGE = 1 << 18, + TILESTATE_ENERGYDAMAGE = 1 << 19, +}; + +enum ZoneType_t { + ZONE_PROTECTION, + ZONE_NOPVP, + ZONE_PVP, + ZONE_NOLOGOUT, + ZONE_NORMAL, +}; + +class TileItemVector : private ItemVector +{ + public: + using ItemVector::begin; + using ItemVector::end; + using ItemVector::rbegin; + using ItemVector::rend; + using ItemVector::size; + using ItemVector::clear; + using ItemVector::at; + using ItemVector::insert; + using ItemVector::erase; + using ItemVector::push_back; + using ItemVector::value_type; + using ItemVector::iterator; + using ItemVector::const_iterator; + using ItemVector::reverse_iterator; + using ItemVector::const_reverse_iterator; + + iterator getBeginDownItem() { + return begin(); + } + const_iterator getBeginDownItem() const { + return begin(); + } + iterator getEndDownItem() { + return begin() + downItemCount; + } + const_iterator getEndDownItem() const { + return begin() + downItemCount; + } + iterator getBeginTopItem() { + return getEndDownItem(); + } + const_iterator getBeginTopItem() const { + return getEndDownItem(); + } + iterator getEndTopItem() { + return end(); + } + const_iterator getEndTopItem() const { + return end(); + } + + uint32_t getTopItemCount() const { + return size() - downItemCount; + } + uint32_t getDownItemCount() const { + return downItemCount; + } + inline Item* getTopTopItem() const { + if (getTopItemCount() == 0) { + return nullptr; + } + return *(getEndTopItem() - 1); + } + inline Item* getTopDownItem() const { + if (downItemCount == 0) { + return nullptr; + } + return *getBeginDownItem(); + } + void addDownItemCount(int32_t increment) { + downItemCount += increment; + } + + private: + uint16_t downItemCount = 0; +}; + +class Tile : public Cylinder +{ + public: + static Tile& nullptr_tile; + Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} + virtual ~Tile(); + + // non-copyable + Tile(const Tile&) = delete; + Tile& operator=(const Tile&) = delete; + + virtual TileItemVector* getItemList() = 0; + virtual const TileItemVector* getItemList() const = 0; + virtual TileItemVector* makeItemList() = 0; + + virtual CreatureVector* getCreatures() = 0; + virtual const CreatureVector* getCreatures() const = 0; + virtual CreatureVector* makeCreatures() = 0; + + int32_t getThrowRange() const final { + return 0; + } + bool isPushable() const final { + return false; + } + + MagicField* getFieldItem() const; + Teleport* getTeleportItem() const; + Mailbox* getMailbox() const; + BedItem* getBedItem() const; + + Creature* getTopCreature() const; + const Creature* getBottomCreature() const; + Creature* getTopVisibleCreature(const Creature* creature) const; + const Creature* getBottomVisibleCreature(const Creature* creature) const; + Item* getTopTopItem() const; + Item* getTopDownItem() const; + bool isMoveableBlocking() const; + Thing* getTopVisibleThing(const Creature* creature); + Item* getItemByTopOrder(int32_t topOrder); + + size_t getThingCount() const { + size_t thingCount = getCreatureCount() + getItemCount(); + if (ground) { + thingCount++; + } + return thingCount; + } + // If these return != 0 the associated vectors are guaranteed to exists + size_t getCreatureCount() const; + size_t getItemCount() const; + uint32_t getTopItemCount() const; + uint32_t getDownItemCount() const; + + bool hasProperty(ITEMPROPERTY prop) const; + bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; + + inline bool hasFlag(uint32_t flag) const { + return hasBitSet(flag, this->flags); + } + inline void setFlag(uint32_t flag) { + this->flags |= flag; + } + inline void resetFlag(uint32_t flag) { + this->flags &= ~flag; + } + + ZoneType_t getZone() const { + if (hasFlag(TILESTATE_PROTECTIONZONE)) { + return ZONE_PROTECTION; + } else if (hasFlag(TILESTATE_NOPVPZONE)) { + return ZONE_NOPVP; + } else if (hasFlag(TILESTATE_PVPZONE)) { + return ZONE_PVP; + } else { + return ZONE_NORMAL; + } + } + + bool hasHeight(uint32_t n) const; + + std::string getDescription(int32_t lookDistance) const final; + + int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; + int32_t getStackposOfCreature(const Player* player, const Creature* creature) const; + int32_t getStackposOfItem(const Player* player, const Item* item) const; + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + ReturnValue queryMaxCount(int32_t index, const Thing& thing, uint32_t count, + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; + Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; + + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) override; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; + void replaceThing(uint32_t index, Thing* thing) final; + + void removeThing(Thing* thing, uint32_t count) final; + + void removeCreature(Creature* creature); + + int32_t getThingIndex(const Thing* thing) const final; + size_t getFirstIndex() const final; + size_t getLastIndex() const final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const final; + Thing* getThing(size_t index) const final; + + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) final; + + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) override; + + const Position& getPosition() const final { + return tilePos; + } + + bool isRemoved() const final { + return false; + } + + Item* getUseItem() const; + + Item* getGround() const { + return ground; + } + void setGround(Item* item) { + ground = item; + } + + private: + void onAddTileItem(Item* item); + void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); + void onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& list); + + void setTileFlags(const Item* item); + void resetTileFlags(const Item* item); + + protected: + Item* ground = nullptr; + Position tilePos; + uint32_t flags = 0; +}; + +// Used for walkable tiles, where there is high likeliness of +// items being added/removed +class DynamicTile : public Tile +{ + // By allocating the vectors in-house, we avoid some memory fragmentation + TileItemVector items; + CreatureVector creatures; + + public: + DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~DynamicTile(); + + // non-copyable + DynamicTile(const DynamicTile&) = delete; + DynamicTile& operator=(const DynamicTile&) = delete; + + TileItemVector* getItemList() final { + return &items; + } + const TileItemVector* getItemList() const final { + return &items; + } + TileItemVector* makeItemList() final { + return &items; + } + + CreatureVector* getCreatures() final { + return &creatures; + } + const CreatureVector* getCreatures() const final { + return &creatures; + } + CreatureVector* makeCreatures() final { + return &creatures; + } +}; + +// For blocking tiles, where we very rarely actually have items +class StaticTile final : public Tile +{ + // We very rarely even need the vectors, so don't keep them in memory + std::unique_ptr items; + std::unique_ptr creatures; + + public: + StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} + ~StaticTile(); + + // non-copyable + StaticTile(const StaticTile&) = delete; + StaticTile& operator=(const StaticTile&) = delete; + + TileItemVector* getItemList() final { + return items.get(); + } + const TileItemVector* getItemList() const final { + return items.get(); + } + TileItemVector* makeItemList() final { + if (!items) { + items.reset(new TileItemVector); + } + return items.get(); + } + + CreatureVector* getCreatures() final { + return creatures.get(); + } + const CreatureVector* getCreatures() const final { + return creatures.get(); + } + CreatureVector* makeCreatures() final { + if (!creatures) { + creatures.reset(new CreatureVector); + } + return creatures.get(); + } +}; + +inline Tile::~Tile() +{ + delete ground; +} + +inline StaticTile::~StaticTile() +{ + if (items) { + for (Item* item : *items) { + item->decrementReferenceCounter(); + } + } +} + +inline DynamicTile::~DynamicTile() +{ + for (Item* item : items) { + item->decrementReferenceCounter(); + } +} + +#endif diff --git a/src/tools.cpp b/src/tools.cpp new file mode 100644 index 0000000..b2b299b --- /dev/null +++ b/src/tools.cpp @@ -0,0 +1,1139 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "tools.h" +#include "configmanager.h" + +extern ConfigManager g_config; + +void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result) +{ + std::cout << '[' << where << "] Failed to load " << fileName << ": " << result.description() << std::endl; + + FILE* file = fopen(fileName.c_str(), "rb"); + if (!file) { + return; + } + + char buffer[32768]; + uint32_t currentLine = 1; + std::string line; + + size_t offset = static_cast(result.offset); + size_t lineOffsetPosition = 0; + size_t index = 0; + size_t bytes; + do { + bytes = fread(buffer, 1, 32768, file); + for (size_t i = 0; i < bytes; ++i) { + char ch = buffer[i]; + if (ch == '\n') { + if ((index + i) >= offset) { + lineOffsetPosition = line.length() - ((index + i) - offset); + bytes = 0; + break; + } + ++currentLine; + line.clear(); + } else { + line.push_back(ch); + } + } + index += bytes; + } while (bytes == 32768); + fclose(file); + + std::cout << "Line " << currentLine << ':' << std::endl; + std::cout << line << std::endl; + for (size_t i = 0; i < lineOffsetPosition; i++) { + if (line[i] == '\t') { + std::cout << '\t'; + } else { + std::cout << ' '; + } + } + std::cout << '^' << std::endl; +} + +inline static uint32_t circularShift(int bits, uint32_t value) +{ + return (value << bits) | (value >> (32 - bits)); +} + +static void processSHA1MessageBlock(const uint8_t* messageBlock, uint32_t* H) +{ + uint32_t W[80]; + for (int i = 0; i < 16; ++i) { + const size_t offset = i << 2; + W[i] = messageBlock[offset] << 24 | messageBlock[offset + 1] << 16 | messageBlock[offset + 2] << 8 | messageBlock[offset + 3]; + } + + for (int i = 16; i < 80; ++i) { + W[i] = circularShift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]); + } + + uint32_t A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; + + for (int i = 0; i < 20; ++i) { + const uint32_t tmp = circularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 20; i < 40; ++i) { + const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 40; i < 60; ++i) { + const uint32_t tmp = circularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + for (int i = 60; i < 80; ++i) { + const uint32_t tmp = circularShift(5, A) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6; + E = D; D = C; C = circularShift(30, B); B = A; A = tmp; + } + + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; +} + +std::string transformToSHA1(const std::string& input) +{ + uint32_t H[] = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0 + }; + + uint8_t messageBlock[64]; + size_t index = 0; + + uint32_t length_low = 0; + uint32_t length_high = 0; + for (char ch : input) { + messageBlock[index++] = ch; + + length_low += 8; + if (length_low == 0) { + length_high++; + } + + if (index == 64) { + processSHA1MessageBlock(messageBlock, H); + index = 0; + } + } + + messageBlock[index++] = 0x80; + + if (index > 56) { + while (index < 64) { + messageBlock[index++] = 0; + } + + processSHA1MessageBlock(messageBlock, H); + index = 0; + } + + while (index < 56) { + messageBlock[index++] = 0; + } + + messageBlock[56] = length_high >> 24; + messageBlock[57] = length_high >> 16; + messageBlock[58] = length_high >> 8; + messageBlock[59] = length_high; + + messageBlock[60] = length_low >> 24; + messageBlock[61] = length_low >> 16; + messageBlock[62] = length_low >> 8; + messageBlock[63] = length_low; + + processSHA1MessageBlock(messageBlock, H); + + char hexstring[41]; + static const char hexDigits[] = {"0123456789abcdef"}; + for (int hashByte = 20; --hashByte >= 0;) { + const uint8_t byte = H[hashByte >> 2] >> (((3 - hashByte) & 3) << 3); + index = hashByte << 1; + hexstring[index] = hexDigits[byte >> 4]; + hexstring[index + 1] = hexDigits[byte & 15]; + } + return std::string(hexstring, 40); +} + +uint8_t getLiquidColor(uint8_t type) +{ + uint8_t result = 0; + switch (type) + { + case 1: + result = 1; + break; + case 0: + result = 0; + break; + case 6: + result = 4; + break; + case 3: + case 4: + case 7: + result = 3; + break; + case 9: + result = 6; + break; + case 2: + case 10: + result = 7; + break; + case 5: + case 11: + result = 2; + break; + case 8: + case 12: + result = 5; + break; + default: + result = 0; + break; + } + return result; +} + +void extractArticleAndName(std::string& data, std::string& article, std::string& name) +{ + std::string xarticle = data.substr(0, 3); + if (xarticle == "an ") + { + name = data.substr(3, data.size()); + article = "an"; + } else { + xarticle = data.substr(0, 2); + if (xarticle == "a ") + { + name = data.substr(2, data.size()); + article = "a"; + } else { + name = data; + article = ""; + } + } +} + +std::string pluralizeString(std::string str) +{ + if (str == "meat") return "meat"; + + int n = str.length(); + char ch = str[n - 1]; + char ch2 = str[n - 2]; + + std::string str2; + if (ch == 'y') + str2 = str.substr(0, n - 1) + "ies"; + else if (ch == 'o' || ch == 's' || ch == 'x') + str2 = str + "es"; + else if (ch == 'h'&& ch2 == 'c') + str2 = str + "es"; + else if (ch == 'f') + str2 = str.substr(0, n - 1) + "ves"; + else if (ch == 'e'&&ch2 == 'f') + str2 = str.substr(0, n - 2) + "ves"; + else + str2 = str + "s"; + + return str2; +} + +void replaceString(std::string& str, const std::string& sought, const std::string& replacement) +{ + size_t pos = 0; + size_t start = 0; + size_t soughtLen = sought.length(); + size_t replaceLen = replacement.length(); + + while ((pos = str.find(sought, start)) != std::string::npos) { + str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); + start = pos + replaceLen; + } +} + +void trim_right(std::string& source, char t) +{ + source.erase(source.find_last_not_of(t) + 1); +} + +void trim_left(std::string& source, char t) +{ + source.erase(0, source.find_first_not_of(t)); +} + +void toLowerCaseString(std::string& source) +{ + std::transform(source.begin(), source.end(), source.begin(), tolower); +} + +std::string asLowerCaseString(std::string source) +{ + toLowerCaseString(source); + return source; +} + +std::string asUpperCaseString(std::string source) +{ + std::transform(source.begin(), source.end(), source.begin(), toupper); + return source; +} + +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +{ + StringVec returnVector; + std::string::size_type start = 0, end = 0; + + while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { + returnVector.push_back(inString.substr(start, end - start)); + start = end + separator.size(); + } + + returnVector.push_back(inString.substr(start)); + return returnVector; +} + +IntegerVec vectorAtoi(const StringVec& stringVector) +{ + IntegerVec returnVector; + for (const auto& string : stringVector) { + returnVector.push_back(std::stoi(string)); + } + return returnVector; +} + +std::mt19937& getRandomGenerator() +{ + static std::random_device rd; + static std::mt19937 generator(rd()); + return generator; +} + +int32_t uniform_random(int32_t minNumber, int32_t maxNumber) +{ + static std::uniform_int_distribution uniformRand; + if (minNumber == maxNumber) { + return minNumber; + } else if (minNumber > maxNumber) { + std::swap(minNumber, maxNumber); + } + return uniformRand(getRandomGenerator(), std::uniform_int_distribution::param_type(minNumber, maxNumber)); +} + +int32_t normal_random(int32_t minNumber, int32_t maxNumber) +{ + static std::normal_distribution normalRand(0.5f, 0.25f); + if (minNumber == maxNumber) { + return minNumber; + } else if (minNumber > maxNumber) { + std::swap(minNumber, maxNumber); + } + + int32_t increment; + const int32_t diff = maxNumber - minNumber; + const float v = normalRand(getRandomGenerator()); + if (v < 0.0) { + increment = diff / 2; + } else if (v > 1.0) { + increment = (diff + 1) / 2; + } else { + increment = round(v * diff); + } + return minNumber + increment; +} + +bool boolean_random(double probability/* = 0.5*/) +{ + static std::bernoulli_distribution booleanRand; + return booleanRand(getRandomGenerator(), std::bernoulli_distribution::param_type(probability)); +} + +void trimString(std::string& str) +{ + str.erase(str.find_last_not_of(' ') + 1); + str.erase(0, str.find_first_not_of(' ')); +} + +std::string convertIPToString(uint32_t ip) +{ + char buffer[17]; + + int res = sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24)); + if (res < 0) { + return {}; + } + + return buffer; +} + +std::string formatDate(time_t time) +{ + const tm* tms = localtime(&time); + if (!tms) { + return {}; + } + + char buffer[20]; + int res = sprintf(buffer, "%02d/%02d/%04d %02d:%02d:%02d", tms->tm_mday, tms->tm_mon + 1, tms->tm_year + 1900, tms->tm_hour, tms->tm_min, tms->tm_sec); + if (res < 0) { + return {}; + } + return {buffer, 19}; +} + +std::string formatDateShort(time_t time) +{ + const tm* tms = localtime(&time); + if (!tms) { + return {}; + } + + char buffer[12]; + size_t res = strftime(buffer, 12, "%d %b %Y", tms); + if (res == 0) { + return {}; + } + return {buffer, 11}; +} + +Direction getDirection(const std::string& string) +{ + Direction direction = DIRECTION_NORTH; + + if (string == "north" || string == "n" || string == "0") { + direction = DIRECTION_NORTH; + } else if (string == "east" || string == "e" || string == "1") { + direction = DIRECTION_EAST; + } else if (string == "south" || string == "s" || string == "2") { + direction = DIRECTION_SOUTH; + } else if (string == "west" || string == "w" || string == "3") { + direction = DIRECTION_WEST; + } else if (string == "southwest" || string == "south west" || string == "south-west" || string == "sw" || string == "4") { + direction = DIRECTION_SOUTHWEST; + } else if (string == "southeast" || string == "south east" || string == "south-east" || string == "se" || string == "5") { + direction = DIRECTION_SOUTHEAST; + } else if (string == "northwest" || string == "north west" || string == "north-west" || string == "nw" || string == "6") { + direction = DIRECTION_NORTHWEST; + } else if (string == "northeast" || string == "north east" || string == "north-east" || string == "ne" || string == "7") { + direction = DIRECTION_NORTHEAST; + } + + return direction; +} + +Position getNextPosition(Direction direction, Position pos) +{ + switch (direction) { + case DIRECTION_NORTH: + pos.y--; + break; + + case DIRECTION_SOUTH: + pos.y++; + break; + + case DIRECTION_WEST: + pos.x--; + break; + + case DIRECTION_EAST: + pos.x++; + break; + + case DIRECTION_SOUTHWEST: + pos.x--; + pos.y++; + break; + + case DIRECTION_NORTHWEST: + pos.x--; + pos.y--; + break; + + case DIRECTION_NORTHEAST: + pos.x++; + pos.y--; + break; + + case DIRECTION_SOUTHEAST: + pos.x++; + pos.y++; + break; + + default: + break; + } + + return pos; +} + +Direction getDirectionTo(const Position& from, const Position& to) +{ + Direction dir; + + int32_t x_offset = Position::getOffsetX(from, to); + if (x_offset < 0) { + dir = DIRECTION_EAST; + x_offset = std::abs(x_offset); + } else { + dir = DIRECTION_WEST; + } + + int32_t y_offset = Position::getOffsetY(from, to); + if (y_offset >= 0) { + if (y_offset > x_offset) { + dir = DIRECTION_NORTH; + } else if (y_offset == x_offset) { + if (dir == DIRECTION_EAST) { + dir = DIRECTION_NORTHEAST; + } else { + dir = DIRECTION_NORTHWEST; + } + } + } else { + y_offset = std::abs(y_offset); + if (y_offset > x_offset) { + dir = DIRECTION_SOUTH; + } else if (y_offset == x_offset) { + if (dir == DIRECTION_EAST) { + dir = DIRECTION_SOUTHEAST; + } else { + dir = DIRECTION_SOUTHWEST; + } + } + } + return dir; +} + +struct MagicEffectNames { + const char* name; + MagicEffectClasses effect; +}; + +struct ShootTypeNames { + const char* name; + ShootType_t shoot; +}; + +struct CombatTypeNames { + const char* name; + CombatType_t combat; +}; + +struct AmmoTypeNames { + const char* name; + Ammo_t ammoType; +}; + +struct WeaponActionNames { + const char* name; + WeaponAction_t weaponAction; +}; + +struct SkullNames { + const char* name; + Skulls_t skull; +}; + +struct FluidNames { + const char* name; + FluidTypes_t fluidType; +}; + +MagicEffectNames magicEffectNames[] = { + {"redspark", CONST_ME_DRAWBLOOD}, + {"bluebubble", CONST_ME_LOSEENERGY}, + {"poff", CONST_ME_POFF}, + {"yellowspark", CONST_ME_BLOCKHIT}, + {"explosionarea", CONST_ME_EXPLOSIONAREA}, + {"explosion", CONST_ME_EXPLOSIONHIT}, + {"firearea", CONST_ME_FIREAREA}, + {"yellowbubble", CONST_ME_YELLOW_RINGS}, + {"greenbubble", CONST_ME_GREEN_RINGS}, + {"blackspark", CONST_ME_HITAREA}, + {"teleport", CONST_ME_TELEPORT}, + {"energy", CONST_ME_ENERGYHIT}, + {"blueshimmer", CONST_ME_MAGIC_BLUE}, + {"redshimmer", CONST_ME_MAGIC_RED}, + {"greenshimmer", CONST_ME_MAGIC_GREEN}, + {"fire", CONST_ME_HITBYFIRE}, + {"greenspark", CONST_ME_HITBYPOISON}, + {"mortarea", CONST_ME_MORTAREA}, + {"greennote", CONST_ME_SOUND_GREEN}, + {"rednote", CONST_ME_SOUND_RED}, + {"poison", CONST_ME_POISONAREA}, + {"yellownote", CONST_ME_SOUND_YELLOW}, + {"purplenote", CONST_ME_SOUND_PURPLE}, + {"bluenote", CONST_ME_SOUND_BLUE}, + {"whitenote", CONST_ME_SOUND_WHITE}, +}; + +ShootTypeNames shootTypeNames[] = { + {"spear", CONST_ANI_SPEAR}, + {"bolt", CONST_ANI_BOLT}, + {"arrow", CONST_ANI_ARROW}, + {"fire", CONST_ANI_FIRE}, + {"energy", CONST_ANI_ENERGY}, + {"poisonarrow", CONST_ANI_POISONARROW}, + {"burstarrow", CONST_ANI_BURSTARROW}, + {"throwingstar", CONST_ANI_THROWINGSTAR}, + {"throwingknife", CONST_ANI_THROWINGKNIFE}, + {"smallstone", CONST_ANI_SMALLSTONE}, + {"death", CONST_ANI_DEATH}, + {"largerock", CONST_ANI_LARGEROCK}, + {"snowball", CONST_ANI_SNOWBALL}, + {"powerbolt", CONST_ANI_POWERBOLT}, + {"poison", CONST_ANI_POISON}, +}; + +CombatTypeNames combatTypeNames[] = { + {"physical", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"poison", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"undefined", COMBAT_UNDEFINEDDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"healing", COMBAT_HEALING}, +}; + +AmmoTypeNames ammoTypeNames[] = { + {"spear", AMMO_SPEAR}, + {"bolt", AMMO_BOLT}, + {"arrow", AMMO_ARROW}, + {"poisonarrow", AMMO_ARROW}, + {"burstarrow", AMMO_ARROW}, + {"throwingstar", AMMO_THROWINGSTAR}, + {"throwingknife", AMMO_THROWINGKNIFE}, + {"smallstone", AMMO_STONE}, + {"largerock", AMMO_STONE}, + {"snowball", AMMO_SNOWBALL}, + {"powerbolt", AMMO_BOLT}, +}; + +WeaponActionNames weaponActionNames[] = { + {"move", WEAPONACTION_MOVE}, + {"removecharge", WEAPONACTION_REMOVECHARGE}, + {"removecount", WEAPONACTION_REMOVECOUNT}, +}; + +SkullNames skullNames[] = { + {"none", SKULL_NONE}, + {"yellow", SKULL_YELLOW}, + {"green", SKULL_GREEN}, + {"white", SKULL_WHITE}, + {"red", SKULL_RED}, +}; + +FluidNames fluidNames[] = { + {"none", FLUID_NONE}, + {"water", FLUID_WATER}, + {"wine", FLUID_WINE}, + {"beer", FLUID_BEER}, + {"mud", FLUID_MUD}, + {"blood", FLUID_BLOOD}, + {"slime", FLUID_SLIME}, + {"oil", FLUID_OIL}, + {"urine", FLUID_URINE}, + {"milk", FLUID_MILK}, + {"manafluid", FLUID_MANAFLUID}, + {"lifefluid", FLUID_LIFEFLUID}, + {"lemonade", FLUID_LEMONADE} +}; + +MagicEffectClasses getMagicEffect(const std::string& strValue) +{ + for (auto& magicEffectName : magicEffectNames) { + if (strcasecmp(strValue.c_str(), magicEffectName.name) == 0) { + return magicEffectName.effect; + } + } + return CONST_ME_NONE; +} + +ShootType_t getShootType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(shootTypeNames) / sizeof(ShootTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), shootTypeNames[i].name) == 0) { + return shootTypeNames[i].shoot; + } + } + return CONST_ANI_NONE; +} + +CombatType_t getCombatType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), combatTypeNames[i].name) == 0) { + return combatTypeNames[i].combat; + } + } + return COMBAT_NONE; +} + +std::string getCombatName(CombatType_t combatType) +{ + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (combatTypeNames[i].combat == combatType) { + return combatTypeNames[i].name; + } + } + return "unknown"; +} + +Ammo_t getAmmoType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(ammoTypeNames) / sizeof(AmmoTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), ammoTypeNames[i].name) == 0) { + return ammoTypeNames[i].ammoType; + } + } + return AMMO_NONE; +} + +WeaponAction_t getWeaponAction(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(weaponActionNames) / sizeof(WeaponActionNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), weaponActionNames[i].name) == 0) { + return weaponActionNames[i].weaponAction; + } + } + return WEAPONACTION_NONE; +} + +Skulls_t getSkullType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(skullNames) / sizeof(SkullNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), skullNames[i].name) == 0) { + return skullNames[i].skull; + } + } + return SKULL_NONE; +} + +FluidTypes_t getFluidType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(fluidNames) / sizeof(FluidNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), fluidNames[i].name) == 0) { + return fluidNames[i].fluidType; + } + } + return FLUID_NONE; +} + +std::string getSkillName(uint8_t skillid) +{ + switch (skillid) { + case SKILL_FIST: + return "fist fighting"; + + case SKILL_CLUB: + return "club fighting"; + + case SKILL_SWORD: + return "sword fighting"; + + case SKILL_AXE: + return "axe fighting"; + + case SKILL_DISTANCE: + return "distance fighting"; + + case SKILL_SHIELD: + return "shielding"; + + case SKILL_FISHING: + return "fishing"; + + case SKILL_MAGLEVEL: + return "magic level"; + + case SKILL_LEVEL: + return "level"; + + default: + return "unknown"; + } +} + +std::string ucfirst(std::string str) +{ + for (char& i : str) { + if (i != ' ') { + i = toupper(i); + break; + } + } + return str; +} + +std::string ucwords(std::string str) +{ + size_t strLength = str.length(); + if (strLength == 0) { + return str; + } + + str[0] = toupper(str.front()); + for (size_t i = 1; i < strLength; ++i) { + if (str[i - 1] == ' ') { + str[i] = toupper(str[i]); + } + } + + return str; +} + +bool booleanString(const std::string& str) +{ + if (str.empty()) { + return false; + } + + char ch = tolower(str.front()); + return ch != 'f' && ch != 'n' && ch != '0'; +} + +std::string getWeaponName(WeaponType_t weaponType) +{ + switch (weaponType) { + case WEAPON_SWORD: return "sword"; + case WEAPON_CLUB: return "club"; + case WEAPON_AXE: return "axe"; + case WEAPON_DISTANCE: return "distance"; + case WEAPON_WAND: return "wand"; + case WEAPON_AMMO: return "ammunition"; + default: return std::string(); + } +} + +size_t combatTypeToIndex(CombatType_t combatType) +{ + switch (combatType) { + case COMBAT_PHYSICALDAMAGE: + return 0; + case COMBAT_ENERGYDAMAGE: + return 1; + case COMBAT_EARTHDAMAGE: + return 2; + case COMBAT_FIREDAMAGE: + return 3; + case COMBAT_UNDEFINEDDAMAGE: + return 4; + case COMBAT_LIFEDRAIN: + return 5; + case COMBAT_MANADRAIN: + return 6; + case COMBAT_HEALING: + return 7; + default: + return 0; + } +} + +CombatType_t indexToCombatType(size_t v) +{ + return static_cast(1 << v); +} + +itemAttrTypes stringToItemAttribute(const std::string& str) +{ + if (str == "aid") { + return ITEM_ATTRIBUTE_ACTIONID; + } else if (str == "mid") { + return ITEM_ATTRIBUTE_MOVEMENTID; + } else if (str == "description") { + return ITEM_ATTRIBUTE_DESCRIPTION; + } else if (str == "text") { + return ITEM_ATTRIBUTE_TEXT; + } else if (str == "date") { + return ITEM_ATTRIBUTE_DATE; + } else if (str == "writer") { + return ITEM_ATTRIBUTE_WRITER; + } else if (str == "name") { + return ITEM_ATTRIBUTE_NAME; + } else if (str == "article") { + return ITEM_ATTRIBUTE_ARTICLE; + } else if (str == "pluralname") { + return ITEM_ATTRIBUTE_PLURALNAME; + } else if (str == "weight") { + return ITEM_ATTRIBUTE_WEIGHT; + } else if (str == "attack") { + return ITEM_ATTRIBUTE_ATTACK; + } else if (str == "defense") { + return ITEM_ATTRIBUTE_DEFENSE; + } else if (str == "armor") { + return ITEM_ATTRIBUTE_ARMOR; + } else if (str == "shootrange") { + return ITEM_ATTRIBUTE_SHOOTRANGE; + } else if (str == "owner") { + return ITEM_ATTRIBUTE_OWNER; + } else if (str == "duration") { + return ITEM_ATTRIBUTE_DURATION; + } else if (str == "decaystate") { + return ITEM_ATTRIBUTE_DECAYSTATE; + } else if (str == "corpseowner") { + return ITEM_ATTRIBUTE_CORPSEOWNER; + } else if (str == "charges") { + return ITEM_ATTRIBUTE_CHARGES; + } else if (str == "fluidtype") { + return ITEM_ATTRIBUTE_FLUIDTYPE; + } else if (str == "doorid") { + return ITEM_ATTRIBUTE_DOORID; + } + return ITEM_ATTRIBUTE_NONE; +} + +std::string getFirstLine(const std::string& str) +{ + std::string firstLine; + firstLine.reserve(str.length()); + for (const char c : str) { + if (c == '\n') { + break; + } + firstLine.push_back(c); + } + return firstLine; +} + +const char* getReturnMessage(ReturnValue value) +{ + switch (value) { + case RETURNVALUE_DESTINATIONOUTOFREACH: + return "Destination is out of reach."; + + case RETURNVALUE_NOTMOVEABLE: + return "You cannot move this object."; + + case RETURNVALUE_DROPTWOHANDEDITEM: + return "Drop the double-handed object first."; + + case RETURNVALUE_BOTHHANDSNEEDTOBEFREE: + return "Both hands need to be free."; + + case RETURNVALUE_CANNOTBEDRESSED: + return "You cannot dress this object there."; + + case RETURNVALUE_PUTTHISOBJECTINYOURHAND: + return "Put this object in your hand."; + + case RETURNVALUE_PUTTHISOBJECTINBOTHHANDS: + return "Put this object in both hands."; + + case RETURNVALUE_CANONLYUSEONEWEAPON: + return "You may only use one weapon."; + + case RETURNVALUE_TOOFARAWAY: + return "Too far away."; + + case RETURNVALUE_FIRSTGODOWNSTAIRS: + return "First go downstairs."; + + case RETURNVALUE_FIRSTGOUPSTAIRS: + return "First go upstairs."; + + case RETURNVALUE_NOTENOUGHCAPACITY: + return "This object is too heavy for you to carry."; + + case RETURNVALUE_CONTAINERNOTENOUGHROOM: + return "You cannot put more objects in this container."; + + case RETURNVALUE_NEEDEXCHANGE: + case RETURNVALUE_NOTENOUGHROOM: + return "There is not enough room."; + + case RETURNVALUE_CANNOTPICKUP: + return "You cannot take this object."; + + case RETURNVALUE_CANNOTTHROW: + return "You cannot throw there."; + + case RETURNVALUE_THEREISNOWAY: + return "There is no way."; + + case RETURNVALUE_THISISIMPOSSIBLE: + return "This is impossible."; + + case RETURNVALUE_PLAYERISPZLOCKED: + return "You can not enter a protection zone after attacking another player."; + + case RETURNVALUE_PLAYERISNOTINVITED: + return "You are not invited."; + + case RETURNVALUE_CREATUREDOESNOTEXIST: + return "Creature does not exist."; + + case RETURNVALUE_DEPOTISFULL: + return "You cannot put more items in this depot."; + + case RETURNVALUE_CANNOTUSETHISOBJECT: + return "You cannot use this object."; + + case RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE: + return "A player with this name is not online."; + + case RETURNVALUE_NOTREQUIREDLEVELTOUSERUNE: + return "You do not have the required magic level to use this rune."; + + case RETURNVALUE_YOUAREALREADYTRADING: + return "You are already trading."; + + case RETURNVALUE_THISPLAYERISALREADYTRADING: + return "This player is already trading."; + + case RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT: + return "You may not logout during or immediately after a fight!"; + + case RETURNVALUE_DIRECTPLAYERSHOOT: + return "You are not allowed to shoot directly on players."; + + case RETURNVALUE_NOTENOUGHLEVEL: + return "You do not have enough level."; + + case RETURNVALUE_NOTENOUGHMAGICLEVEL: + return "You do not have enough magic level."; + + case RETURNVALUE_NOTENOUGHMANA: + return "You do not have enough mana."; + + case RETURNVALUE_NOTENOUGHSOUL: + return "You do not have enough soul."; + + case RETURNVALUE_YOUAREEXHAUSTED: + return "You are exhausted."; + + case RETURNVALUE_CANONLYUSETHISRUNEONCREATURES: + return "You can only use this rune on creatures."; + + case RETURNVALUE_PLAYERISNOTREACHABLE: + return "Player is not reachable."; + + case RETURNVALUE_CREATUREISNOTREACHABLE: + return "Creature is not reachable."; + + case RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE: + return "This action is not permitted in a protection zone."; + + case RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER: + return "You may not attack this player."; + + case RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE: + return "You may not attack this creature."; + + case RETURNVALUE_YOUMAYNOTATTACKAPERSONINPROTECTIONZONE: + return "You may not attack a person in a protection zone."; + + case RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE: + return "You may not attack a person while you are in a protection zone."; + + case RETURNVALUE_YOUCANONLYUSEITONCREATURES: + return "You can only use it on creatures."; + + case RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS: + return "Turn secure mode off if you really want to attack unmarked players."; + + case RETURNVALUE_YOUNEEDPREMIUMACCOUNT: + return "You need a premium account."; + + case RETURNVALUE_YOUNEEDTOLEARNTHISSPELL: + return "You need to learn this spell first."; + + case RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL: + return "Your vocation cannot use this spell."; + + case RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL: + return "You need to equip a weapon to use this spell."; + + case RETURNVALUE_PLAYERISPZLOCKEDLEAVEPVPZONE: + return "You can not leave a pvp zone after attacking another player."; + + case RETURNVALUE_PLAYERISPZLOCKEDENTERPVPZONE: + return "You can not enter a pvp zone after attacking another player."; + + case RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE: + return "This action is not permitted in a non pvp zone."; + + case RETURNVALUE_YOUCANNOTLOGOUTHERE: + return "You can not logout here."; + + case RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL: + return "You need a magic item to cast this spell."; + + case RETURNVALUE_CANNOTCONJUREITEMHERE: + return "You cannot conjure items here."; + + case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS: + return "You need to split your spears first."; + + case RETURNVALUE_NAMEISTOOAMBIGIOUS: + return "Name is too ambigious."; + + case RETURNVALUE_CANONLYUSEONESHIELD: + return "You may use only one shield."; + + case RETURNVALUE_NOPARTYMEMBERSINRANGE: + return "No party members in range."; + + case RETURNVALUE_YOUARENOTTHEOWNER: + return "You are not the owner."; + + default: // RETURNVALUE_NOTPOSSIBLE, etc + return "Sorry, not possible."; + } +} + +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret) +{ + if (!boost::filesystem::exists(root)) { + return; + } + + if (boost::filesystem::is_directory(root)) + { + boost::filesystem::recursive_directory_iterator it(root); + boost::filesystem::recursive_directory_iterator endit; + while (it != endit) + { + if (boost::filesystem::is_regular_file(*it) && it->path().extension() == ext) + { + ret.push_back(it->path().filename()); + } + ++it; + } + } +} diff --git a/src/tools.h b/src/tools.h new file mode 100644 index 0000000..c4cd4fe --- /dev/null +++ b/src/tools.h @@ -0,0 +1,106 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 +#define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 + +#include +#include + +#include "position.h" +#include "const.h" +#include "enums.h" + +void printXMLError(const std::string& where, const std::string& fileName, const pugi::xml_parse_result& result); + +std::string transformToSHA1(const std::string& input); +uint8_t getLiquidColor(uint8_t type); + +void extractArticleAndName(std::string& data, std::string& article, std::string& name); +std::string pluralizeString(std::string str); +void replaceString(std::string& str, const std::string& sought, const std::string& replacement); +void trim_right(std::string& source, char t); +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); + +typedef std::vector StringVec; +typedef std::vector IntegerVec; + +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); +IntegerVec vectorAtoi(const StringVec& stringVector); +inline bool hasBitSet(uint32_t flag, uint32_t flags) { + return (flags & flag) != 0; +} + +inline bool IsDigit(char c) +{ + return ('0' <= c && c <= '9'); +} + +std::mt19937& getRandomGenerator(); +int32_t uniform_random(int32_t minNumber, int32_t maxNumber); +int32_t normal_random(int32_t minNumber, int32_t maxNumber); +bool boolean_random(double probability = 0.5); + +Direction getDirection(const std::string& string); +Position getNextPosition(Direction direction, Position pos); +Direction getDirectionTo(const Position& from, const Position& to); + +std::string getFirstLine(const std::string& str); + +std::string formatDate(time_t time); +std::string formatDateShort(time_t time); +std::string convertIPToString(uint32_t ip); + +void trimString(std::string& str); + +MagicEffectClasses getMagicEffect(const std::string& strValue); +ShootType_t getShootType(const std::string& strValue); +Ammo_t getAmmoType(const std::string& strValue); +WeaponAction_t getWeaponAction(const std::string& strValue); +CombatType_t getCombatType(const std::string& strValue); +Skulls_t getSkullType(const std::string& strValue); +FluidTypes_t getFluidType(const std::string& strValue); +std::string getCombatName(CombatType_t combatType); + +std::string getSkillName(uint8_t skillid); + +std::string ucfirst(std::string str); +std::string ucwords(std::string str); +bool booleanString(const std::string& str); + +std::string getWeaponName(WeaponType_t weaponType); + +size_t combatTypeToIndex(CombatType_t combatType); +CombatType_t indexToCombatType(size_t v); + +itemAttrTypes stringToItemAttribute(const std::string& str); + +const char* getReturnMessage(ReturnValue value); + +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret); + +inline int64_t OTSYS_TIME() +{ + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +#endif diff --git a/src/town.h b/src/town.h new file mode 100644 index 0000000..a64939b --- /dev/null +++ b/src/town.h @@ -0,0 +1,98 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 +#define FS_TOWN_H_3BE21D2293B44AA4A3D22D25BE1B9350 + +#include "position.h" + +class Town +{ + public: + explicit Town(uint32_t id) : id(id) {} + + const Position& getTemplePosition() const { + return templePosition; + } + const std::string& getName() const { + return name; + } + + void setTemplePos(Position pos) { + templePosition = pos; + } + void setName(std::string name) { + this->name = std::move(name); + } + uint32_t getID() const { + return id; + } + + private: + uint32_t id; + std::string name; + Position templePosition; +}; + +typedef std::map TownMap; + +class Towns +{ + public: + Towns() = default; + ~Towns() { + for (const auto& it : townMap) { + delete it.second; + } + } + + // non-copyable + Towns(const Towns&) = delete; + Towns& operator=(const Towns&) = delete; + + bool addTown(uint32_t townId, Town* town) { + return townMap.emplace(townId, town).second; + } + + Town* getTown(const std::string& townName) const { + for (const auto& it : townMap) { + if (strcasecmp(townName.c_str(), it.second->getName().c_str()) == 0) { + return it.second; + } + } + return nullptr; + } + + Town* getTown(uint32_t townId) const { + auto it = townMap.find(townId); + if (it == townMap.end()) { + return nullptr; + } + return it->second; + } + + const TownMap& getTowns() const { + return townMap; + } + + private: + TownMap townMap; +}; + +#endif diff --git a/src/vocation.cpp b/src/vocation.cpp new file mode 100644 index 0000000..5ac8f36 --- /dev/null +++ b/src/vocation.cpp @@ -0,0 +1,200 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "vocation.h" + +#include "pugicast.h" +#include "tools.h" + +bool Vocations::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/vocations.xml"); + if (!result) { + printXMLError("Error - Vocations::loadFromXml", "data/XML/vocations.xml", result); + return false; + } + + for (auto vocationNode : doc.child("vocations").children()) { + pugi::xml_attribute attr; + if (!(attr = vocationNode.attribute("id"))) { + std::cout << "[Warning - Vocations::loadFromXml] Missing vocation id" << std::endl; + continue; + } + + uint16_t id = pugi::cast(attr.value()); + + auto res = vocationsMap.emplace(std::piecewise_construct, + std::forward_as_tuple(id), std::forward_as_tuple(id)); + Vocation& voc = res.first->second; + + if (!(attr = vocationNode.attribute("flagid"))) { + std::cout << "[Warning - Vocations::loadFromXml] Missing vocation flag id" << std::endl; + continue; + } + + voc.flagid = pugi::cast(attr.value()); + + if ((attr = vocationNode.attribute("name"))) { + voc.name = attr.as_string(); + } + + if ((attr = vocationNode.attribute("description"))) { + voc.description = attr.as_string(); + } + + if ((attr = vocationNode.attribute("gaincap"))) { + voc.gainCap = pugi::cast(attr.value()) * 100; + } + + if ((attr = vocationNode.attribute("gainhp"))) { + voc.gainHP = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmana"))) { + voc.gainMana = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainhpticks"))) { + voc.gainHealthTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainhpamount"))) { + voc.gainHealthAmount = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmanaticks"))) { + voc.gainManaTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainmanaamount"))) { + voc.gainManaAmount = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("manamultiplier"))) { + voc.manaMultiplier = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("attackspeed"))) { + voc.attackSpeed = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("basespeed"))) { + voc.baseSpeed = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("soulmax"))) { + voc.soulMax = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("gainsoulticks"))) { + voc.gainSoulTicks = pugi::cast(attr.value()); + } + + if ((attr = vocationNode.attribute("fromvoc"))) { + voc.fromVocation = pugi::cast(attr.value()); + } + + for (auto childNode : vocationNode.children()) { + if (strcasecmp(childNode.name(), "skill") == 0) { + pugi::xml_attribute skillIdAttribute = childNode.attribute("id"); + if (skillIdAttribute) { + uint16_t skill_id = pugi::cast(skillIdAttribute.value()); + if (skill_id <= SKILL_LAST) { + voc.skillMultipliers[skill_id] = pugi::cast(childNode.attribute("multiplier").value()); + } else { + std::cout << "[Notice - Vocations::loadFromXml] No valid skill id: " << skill_id << " for vocation: " << voc.id << std::endl; + } + } else { + std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; + } + } + } + } + return true; +} + +Vocation* Vocations::getVocation(uint16_t id) +{ + auto it = vocationsMap.find(id); + if (it == vocationsMap.end()) { + std::cout << "[Warning - Vocations::getVocation] Vocation " << id << " not found." << std::endl; + return nullptr; + } + return &it->second; +} + +int32_t Vocations::getVocationId(const std::string& name) const +{ + for (const auto& it : vocationsMap) { + if (strcasecmp(it.second.name.c_str(), name.c_str()) == 0) { + return it.first; + } + } + return -1; +} + +uint16_t Vocations::getPromotedVocation(uint16_t vocationId) const +{ + for (const auto& it : vocationsMap) { + if (it.second.fromVocation == vocationId && it.first != vocationId) { + return it.first; + } + } + return VOCATION_NONE; +} + +uint32_t Vocation::skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20}; + +uint64_t Vocation::getReqSkillTries(uint8_t skill, uint16_t level) +{ + if (skill > SKILL_LAST) { + return 0; + } + + auto it = cacheSkill[skill].find(level); + if (it != cacheSkill[skill].end()) { + return it->second; + } + + uint64_t tries = static_cast(skillBase[skill] * std::pow(static_cast(skillMultipliers[skill]), level - 11)); + cacheSkill[skill][level] = tries; + return tries; +} + +uint64_t Vocation::getReqMana(uint32_t magLevel) +{ + auto it = cacheMana.find(magLevel); + if (it != cacheMana.end()) { + return it->second; + } + + uint64_t reqMana = static_cast(400 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); + uint32_t modResult = reqMana % 20; + if (modResult < 10) { + reqMana -= modResult; + } else { + reqMana -= modResult + 20; + } + + cacheMana[magLevel] = reqMana; + return reqMana; +} diff --git a/src/vocation.h b/src/vocation.h new file mode 100644 index 0000000..ad28e12 --- /dev/null +++ b/src/vocation.h @@ -0,0 +1,133 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D +#define FS_VOCATION_H_ADCAA356C0DB44CEBA994A0D678EC92D + +#include "enums.h" +#include "item.h" + +class Vocation +{ + public: + explicit Vocation(uint16_t id) : id(id) {} + + const std::string& getVocName() const { + return name; + } + const std::string& getVocDescription() const { + return description; + } + uint64_t getReqSkillTries(uint8_t skill, uint16_t level); + uint64_t getReqMana(uint32_t magLevel); + + uint16_t getId() const { + return id; + } + uint16_t getFlagId() const { + return flagid; + } + + uint32_t getHPGain() const { + return gainHP; + } + uint32_t getManaGain() const { + return gainMana; + } + uint32_t getCapGain() const { + return gainCap; + } + + uint32_t getManaGainTicks() const { + return gainManaTicks; + } + uint32_t getManaGainAmount() const { + return gainManaAmount; + } + uint32_t getHealthGainTicks() const { + return gainHealthTicks; + } + uint32_t getHealthGainAmount() const { + return gainHealthAmount; + } + + uint8_t getSoulMax() const { + return soulMax; + } + uint16_t getSoulGainTicks() const { + return gainSoulTicks; + } + + uint32_t getAttackSpeed() const { + return attackSpeed; + } + uint32_t getBaseSpeed() const { + return baseSpeed; + } + + uint32_t getFromVocation() const { + return fromVocation; + } + + protected: + friend class Vocations; + + std::map cacheMana; + std::map cacheSkill[SKILL_LAST + 1]; + + std::string name = "none"; + std::string description; + + float skillMultipliers[SKILL_LAST + 1] = {1.5f, 2.0f, 2.0f, 2.0f, 2.0f, 1.5f, 1.1f}; + float manaMultiplier = 4.0f; + + uint32_t gainHealthTicks = 6; + uint32_t gainHealthAmount = 1; + uint32_t gainManaTicks = 6; + uint32_t gainManaAmount = 1; + uint32_t gainCap = 500; + uint32_t gainMana = 5; + uint32_t gainHP = 5; + uint32_t fromVocation = VOCATION_NONE; + uint32_t attackSpeed = 1500; + uint32_t baseSpeed = 70; + uint16_t id; + uint16_t flagid; + + uint16_t gainSoulTicks = 120; + + uint8_t soulMax = 100; + + static uint32_t skillBase[SKILL_LAST + 1]; +}; + +class Vocations +{ + public: + bool loadFromXml(); + + Vocation* getVocation(uint16_t id); + int32_t getVocationId(const std::string& name) const; + uint16_t getPromotedVocation(uint16_t vocationId) const; + + private: + std::map vocationsMap; +}; + +#endif diff --git a/src/waitlist.cpp b/src/waitlist.cpp new file mode 100644 index 0000000..20acc80 --- /dev/null +++ b/src/waitlist.cpp @@ -0,0 +1,130 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "configmanager.h" +#include "game.h" +#include "waitlist.h" + +extern ConfigManager g_config; +extern Game g_game; + +WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot) +{ + slot = 1; + for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; + } + ++slot; + } + + for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; + } + ++slot; + } + return waitList.end(); +} + +uint32_t WaitingList::getTime(uint32_t slot) +{ + if (slot < 5) { + return 5; + } else if (slot < 10) { + return 10; + } else if (slot < 20) { + return 20; + } else if (slot < 50) { + return 60; + } else { + return 120; + } +} + +uint32_t WaitingList::getTimeout(uint32_t slot) +{ + //timeout is set to 15 seconds longer than expected retry attempt + return getTime(slot) + 15; +} + +bool WaitingList::clientLogin(const Player* player) +{ + if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + return true; + } + + uint32_t maxPlayers = static_cast(g_config.getNumber(ConfigManager::MAX_PLAYERS)); + if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { + return true; + } + + WaitingList::cleanupList(priorityWaitList); + WaitingList::cleanupList(waitList); + + uint32_t slot; + + auto it = findClient(player, slot); + if (it != waitList.end()) { + if ((g_game.getPlayersOnline() + slot) <= maxPlayers) { + //should be able to login now + waitList.erase(it); + return true; + } + + //let them wait a bit longer + it->timeout = OTSYS_TIME() + (getTimeout(slot) * 1000); + return false; + } + + slot = priorityWaitList.size(); + if (player->isPremium()) { + priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + } else { + slot += waitList.size(); + waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + } + return false; +} + +uint32_t WaitingList::getClientSlot(const Player* player) +{ + uint32_t slot; + auto it = findClient(player, slot); + if (it == waitList.end()) { + return 0; + } + return slot; +} + +void WaitingList::cleanupList(WaitList& list) +{ + int64_t time = OTSYS_TIME(); + + auto it = list.begin(), end = list.end(); + while (it != end) { + if ((it->timeout - time) <= 0) { + it = list.erase(it); + } else { + ++it; + } + } +} diff --git a/src/waitlist.h b/src/waitlist.h new file mode 100644 index 0000000..00e7ebf --- /dev/null +++ b/src/waitlist.h @@ -0,0 +1,57 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 +#define FS_WAITLIST_H_7E4299E552E44F10BC4F4E50BF3D7241 + +#include "player.h" + +struct Wait { + constexpr Wait(int64_t timeout, uint32_t playerGUID) : + timeout(timeout), playerGUID(playerGUID) {} + + int64_t timeout; + uint32_t playerGUID; +}; + +typedef std::list WaitList; +typedef WaitList::iterator WaitListIterator; + +class WaitingList +{ + public: + static WaitingList* getInstance() { + static WaitingList waitingList; + return &waitingList; + } + + bool clientLogin(const Player* player); + uint32_t getClientSlot(const Player* player); + static uint32_t getTime(uint32_t slot); + + protected: + WaitList priorityWaitList; + WaitList waitList; + + static uint32_t getTimeout(uint32_t slot); + WaitListIterator findClient(const Player* player, uint32_t& slot); + static void cleanupList(WaitList& list); +}; + +#endif diff --git a/src/wildcardtree.cpp b/src/wildcardtree.cpp new file mode 100644 index 0000000..6a9cac2 --- /dev/null +++ b/src/wildcardtree.cpp @@ -0,0 +1,129 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include + +#include "wildcardtree.h" + +WildcardTreeNode* WildcardTreeNode::getChild(char ch) +{ + auto it = children.find(ch); + if (it == children.end()) { + return nullptr; + } + return &it->second; +} + +const WildcardTreeNode* WildcardTreeNode::getChild(char ch) const +{ + auto it = children.find(ch); + if (it == children.end()) { + return nullptr; + } + return &it->second; +} + +WildcardTreeNode* WildcardTreeNode::addChild(char ch, bool breakpoint) +{ + WildcardTreeNode* child = getChild(ch); + if (child) { + if (breakpoint && !child->breakpoint) { + child->breakpoint = true; + } + } else { + auto pair = children.emplace(std::piecewise_construct, + std::forward_as_tuple(ch), std::forward_as_tuple(breakpoint)); + child = &pair.first->second; + } + return child; +} + +void WildcardTreeNode::insert(const std::string& str) +{ + WildcardTreeNode* cur = this; + + size_t length = str.length() - 1; + for (size_t pos = 0; pos < length; ++pos) { + cur = cur->addChild(str[pos], false); + } + + cur->addChild(str[length], true); +} + +void WildcardTreeNode::remove(const std::string& str) +{ + WildcardTreeNode* cur = this; + + std::stack path; + path.push(cur); + size_t len = str.length(); + for (size_t pos = 0; pos < len; ++pos) { + cur = cur->getChild(str[pos]); + if (!cur) { + return; + } + path.push(cur); + } + + cur->breakpoint = false; + + do { + cur = path.top(); + path.pop(); + + if (!cur->children.empty() || cur->breakpoint || path.empty()) { + break; + } + + cur = path.top(); + + auto it = cur->children.find(str[--len]); + if (it != cur->children.end()) { + cur->children.erase(it); + } + } while (true); +} + +ReturnValue WildcardTreeNode::findOne(const std::string& query, std::string& result) const +{ + const WildcardTreeNode* cur = this; + for (char pos : query) { + cur = cur->getChild(pos); + if (!cur) { + return RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE; + } + } + + result = query; + + do { + size_t size = cur->children.size(); + if (size == 0) { + return RETURNVALUE_NOERROR; + } else if (size > 1 || cur->breakpoint) { + return RETURNVALUE_NAMEISTOOAMBIGIOUS; + } + + auto it = cur->children.begin(); + result += it->first; + cur = &it->second; + } while (true); +} diff --git a/src/wildcardtree.h b/src/wildcardtree.h new file mode 100644 index 0000000..730ed27 --- /dev/null +++ b/src/wildcardtree.h @@ -0,0 +1,49 @@ +/** + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2017 Alejandro Mujica + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB +#define FS_WILDCARDTREE_H_054C9BA46A1D4EA4B7C77ECE60ED4DEB + +#include "enums.h" + +class WildcardTreeNode +{ + public: + explicit WildcardTreeNode(bool breakpoint) : breakpoint(breakpoint) {} + WildcardTreeNode(WildcardTreeNode&& other) = default; + + // non-copyable + WildcardTreeNode(const WildcardTreeNode&) = delete; + WildcardTreeNode& operator=(const WildcardTreeNode&) = delete; + + WildcardTreeNode* getChild(char ch); + const WildcardTreeNode* getChild(char ch) const; + WildcardTreeNode* addChild(char ch, bool breakpoint); + + void insert(const std::string& str); + void remove(const std::string& str); + + ReturnValue findOne(const std::string& query, std::string& result) const; + + private: + std::map children; + bool breakpoint; +}; + +#endif diff --git a/vc14/arch32.props b/vc14/arch32.props new file mode 100644 index 0000000..d2adc41 --- /dev/null +++ b/vc14/arch32.props @@ -0,0 +1,13 @@ + + + + + + + + $(TFS_LIBS) + true + + + + \ No newline at end of file diff --git a/vc14/arch64.props b/vc14/arch64.props new file mode 100644 index 0000000..6c086a5 --- /dev/null +++ b/vc14/arch64.props @@ -0,0 +1,12 @@ + + + + + + + + $(TFS_LIBS64) + + + + \ No newline at end of file diff --git a/vc14/debug.props b/vc14/debug.props new file mode 100644 index 0000000..ab4fb7d --- /dev/null +++ b/vc14/debug.props @@ -0,0 +1,22 @@ + + + + + + true + + + + false + false + EnableFastChecks + MultiThreadedDebugDLL + $(IntDir)\obj_d\ + + + $(TFS_LIBDEPS_D) + + + + + diff --git a/vc14/release.props b/vc14/release.props new file mode 100644 index 0000000..1a6520d --- /dev/null +++ b/vc14/release.props @@ -0,0 +1,19 @@ + + + + + + false + + + + Full + $(IntDir)\obj_r\ + + + UseLinkTimeCodeGeneration + + + + + diff --git a/vc14/settings.props b/vc14/settings.props new file mode 100644 index 0000000..b7fc74a --- /dev/null +++ b/vc14/settings.props @@ -0,0 +1,74 @@ + + + + + $(TFSSDKDir)\LuaJIT\ + $(TFSSDKDir)\mpir\ + $(TFSSDKDir)\mysql-connector-c\ + $(TFSSDKDir)\pugixml\ + _CRT_SECURE_NO_WARNINGS; + $(BOOST_ROOT);$(LUA_DIR)\include;$(GMP_DIR)\include;$(MYSQLC_DIR)\include;$(PUGIXML_DIR)\include; + $(BOOST_ROOT)\lib32-msvc-14.0;$(LUA_DIR)\lib;$(GMP_DIR)\lib;$(MYSQLC_DIR)\lib + $(BOOST_ROOT)\lib64-msvc-14.0;$(LUA_DIR)\lib64;$(GMP_DIR)\lib64;$(MYSQLC_DIR)\lib64 + lua51.lib;mpir.lib;libmysql.lib + lua51.lib;mpir.lib;libmysql.lib + + + false + + + + $(TFS_INCLUDES) + Level3 + true + true + Use + otpch.h + MultiThreadedDLL + + + $(TFS_LIBDEPS) + Default + + + $(PREPROCESSOR_DEFS) + + + + + $(LUA_DIR) + true + + + $(GMP_DIR) + true + + + $(MYSQLC_DIR) + true + + + $(PREPROCESSOR_DEFS) + true + + + $(TFS_INCLUDES) + true + + + $(TFS_LIBS) + true + + + $(TFS_LIBS64) + true + + + $(TFS_LIBDEPS) + true + + + $(TFS_LIBDEPS_D) + + + \ No newline at end of file diff --git a/vc14/theforgottenserver.sln b/vc14/theforgottenserver.sln new file mode 100644 index 0000000..4c37956 --- /dev/null +++ b/vc14/theforgottenserver.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "theforgottenserver", "theforgottenserver.vcxproj", "{A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.ActiveCfg = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|Win32.Build.0 = Debug|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.ActiveCfg = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Debug|x64.Build.0 = Debug|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.ActiveCfg = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|Win32.Build.0 = Release|Win32 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.ActiveCfg = Release|x64 + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/vc14/theforgottenserver.vcxproj b/vc14/theforgottenserver.vcxproj new file mode 100644 index 0000000..10538bb --- /dev/null +++ b/vc14/theforgottenserver.vcxproj @@ -0,0 +1,298 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + Win32Proj + {A10F9657-129F-0FEF-14CB-CEE0B0E5AA3E} + 10.0.17134.0 + + + + Application + true + v141 + + + Application + true + v141 + + + Application + false + v141 + + + Application + false + v141 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. + + + + _CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + Disabled + + + + MachineX86 + true + Console + + + + + + + _CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + ProgramDatabase + + + + + true + Console + + + + + + + + NDEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + + + + MachineX86 + true + Console + true + true + + + + + + + NDEBUG;_CONSOLE;$(PREPROCESSOR_DEFS);%(PreprocessorDefinitions) + MultiThreadedDLL + ProgramDatabase + Level4 + + + + + true + Console + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + otpch.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file