diff --git a/.github/workflows/analysis-reviewdog-cppcheck.yml b/.github/workflows/analysis-reviewdog-cppcheck.yml
index 4098ccbdcda..bca1d0578bb 100644
--- a/.github/workflows/analysis-reviewdog-cppcheck.yml
+++ b/.github/workflows/analysis-reviewdog-cppcheck.yml
@@ -4,13 +4,12 @@ name: Analysis - Review Dog
on:
pull_request:
paths:
- - 'src/**'
+ - "src/**"
push:
paths:
- - 'src/**'
+ - "src/**"
jobs:
-
cppcheck:
runs-on: ubuntu-latest
steps:
@@ -18,7 +17,7 @@ jobs:
if: github.ref != 'refs/heads/main'
uses: fkirc/skip-duplicate-actions@master
with:
- concurrent_skipping: 'same_content'
+ concurrent_skipping: "same_content"
cancel_others: true
- name: Check out code.
diff --git a/.github/workflows/analysis-reviewdog.yml b/.github/workflows/analysis-reviewdog.yml
index b3bf0b562e6..a7d6eddac7b 100644
--- a/.github/workflows/analysis-reviewdog.yml
+++ b/.github/workflows/analysis-reviewdog.yml
@@ -12,7 +12,7 @@ jobs:
if: github.ref != 'refs/heads/main'
uses: fkirc/skip-duplicate-actions@master
with:
- concurrent_skipping: 'same_content'
+ concurrent_skipping: "same_content"
cancel_others: true
- name: Check out code.
@@ -32,11 +32,9 @@ jobs:
luac -v
reviewdog -reporter=github-pr-check -runners=luac
-
luacheck:
runs-on: ubuntu-latest
steps:
-
- name: Check out code.
uses: actions/checkout@main
@@ -54,11 +52,9 @@ jobs:
cd "$GITHUB_WORKSPACE"
reviewdog -reporter=github-pr-check -runners=luacheck
-
shellcheck:
runs-on: ubuntu-latest
steps:
-
- name: Check out code.
uses: actions/checkout@main
@@ -67,14 +63,12 @@ jobs:
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-check
- pattern: '*.sh'
- exclude: './.git/*'
-
+ pattern: "*.sh"
+ exclude: "./.git/*"
xmllint:
runs-on: ubuntu-latest
steps:
-
- name: Check out code.
uses: actions/checkout@main
@@ -92,11 +86,9 @@ jobs:
xmllint --version
reviewdog -reporter=github-pr-check -runners=xmllint
-
yamllint:
runs-on: ubuntu-latest
steps:
-
- name: Check out code.
uses: actions/checkout@main
@@ -106,11 +98,9 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-check
-
hadolint:
runs-on: ubuntu-latest
steps:
-
- name: Check out code
uses: actions/checkout@main
@@ -122,7 +112,6 @@ jobs:
actionlint:
runs-on: ubuntu-latest
steps:
-
- name: Check out code
uses: actions/checkout@main
diff --git a/.github/workflows/analysis-sonarcloud.yml b/.github/workflows/analysis-sonarcloud.yml
index a929886f291..71b910fa995 100644
--- a/.github/workflows/analysis-sonarcloud.yml
+++ b/.github/workflows/analysis-sonarcloud.yml
@@ -3,19 +3,14 @@ name: Analysis - SonarCloud
on:
pull_request_target:
- types: [opened, synchronize, reopened]
+ types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/**'
- push:
- paths:
- - 'src/**'
- branches:
- - main
+ - "src/**"
env:
VCPKG_BUILD_TYPE: debug
CMAKE_BUILD_PARALLEL_LEVEL: 2
- MAKEFLAGS: '-j 2'
+ MAKEFLAGS: "-j 2"
VCPKG_BINARY_SOURCES: clear;default,readwrite
jobs:
diff --git a/.github/workflows/build-docker-dummy.yml b/.github/workflows/build-docker-dummy.yml
new file mode 100644
index 00000000000..7c160ea2245
--- /dev/null
+++ b/.github/workflows/build-docker-dummy.yml
@@ -0,0 +1,22 @@
+---
+name: Build - Docker (dummy)
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths-ignore:
+ - "src/**"
+
+jobs:
+ build_docker_x86:
+ if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
+
+ build_docker_arm:
+ if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index f4366e8fe65..d99ba82878c 100644
--- a/.github/workflows/build-docker.yml
+++ b/.github/workflows/build-docker.yml
@@ -6,22 +6,23 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/**'
+ - "src/**"
+ merge_group:
push:
paths:
- - 'src/**'
+ - "src/**"
branches:
- main
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
build_docker_x86:
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
@@ -35,7 +36,7 @@ jobs:
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.15
with:
- versionSpec: '5.x'
+ versionSpec: "5.x"
- name: Determine Version
id: gitversion
@@ -89,13 +90,6 @@ jobs:
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- if: github.ref != 'refs/heads/main'
- uses: fkirc/skip-duplicate-actions@master
- with:
- concurrent_skipping: 'same_content'
- cancel_others: true
-
- name: Checkout
uses: actions/checkout@main
with:
@@ -104,7 +98,7 @@ jobs:
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.15
with:
- versionSpec: '5.x'
+ versionSpec: "5.x"
- name: Determine Version
id: gitversion
diff --git a/.github/workflows/build-ubuntu-dummy.yml b/.github/workflows/build-ubuntu-dummy.yml
new file mode 100644
index 00000000000..00c4efdef87
--- /dev/null
+++ b/.github/workflows/build-ubuntu-dummy.yml
@@ -0,0 +1,31 @@
+---
+name: Build - Ubuntu (dummy)
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths-ignore:
+ - "src/**"
+
+jobs:
+ job:
+ if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
+ name: ${{ matrix.os }}-${{ matrix.buildtype }}
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-20.04, ubuntu-22.04]
+ buildtype: [linux-release, linux-debug]
+ include:
+ - os: ubuntu-20.04
+ triplet: x64-linux
+ - os: ubuntu-22.04
+ triplet: x64-linux
+
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
+ - name: Checkout repository
+ uses: actions/checkout@main
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
index 7d2543a59bf..cdc54dc33f2 100644
--- a/.github/workflows/build-ubuntu.yml
+++ b/.github/workflows/build-ubuntu.yml
@@ -6,26 +6,27 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/**'
+ - "src/**"
+ merge_group:
push:
paths:
- - 'src/**'
+ - "src/**"
branches:
- main
env:
CMAKE_BUILD_PARALLEL_LEVEL: 2
- MAKEFLAGS: '-j 2'
+ MAKEFLAGS: "-j 2"
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
job:
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
diff --git a/.github/workflows/build-windows-cmake-dummy.yml b/.github/workflows/build-windows-cmake-dummy.yml
new file mode 100644
index 00000000000..35c66277314
--- /dev/null
+++ b/.github/workflows/build-windows-cmake-dummy.yml
@@ -0,0 +1,25 @@
+---
+name: Build - Windows - CMake (dummy)
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths-ignore:
+ - "src/**"
+
+jobs:
+ job:
+ if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
+ name: ${{ matrix.os }}-${{ matrix.buildtype }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [windows-2022]
+ buildtype: [windows-release]
+ include:
+ - os: windows-2022
+ triplet: x64-windows-static
+ packages: >
+ sccache
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
diff --git a/.github/workflows/build-windows-cmake.yml b/.github/workflows/build-windows-cmake.yml
index a59041dffd2..ab0accc857b 100644
--- a/.github/workflows/build-windows-cmake.yml
+++ b/.github/workflows/build-windows-cmake.yml
@@ -5,24 +5,25 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/**'
+ - "src/**"
+ merge_group:
push:
paths:
- - 'src/**'
+ - "src/**"
branches:
- main
env:
CMAKE_BUILD_PARALLEL_LEVEL: 2
- MAKEFLAGS: '-j 2'
+ MAKEFLAGS: "-j 2"
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
job:
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
diff --git a/.github/workflows/build-windows-solution-dummy.yml b/.github/workflows/build-windows-solution-dummy.yml
new file mode 100644
index 00000000000..6df2e27ec8e
--- /dev/null
+++ b/.github/workflows/build-windows-solution-dummy.yml
@@ -0,0 +1,26 @@
+---
+name: Build - Windows - Solution (dummy)
+
+on:
+ workflow_dispatch:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths-ignore:
+ - "src/**"
+
+jobs:
+ job:
+ if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
+ name: ${{ matrix.os }}-${{ matrix.buildtype }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [windows-2022]
+ buildtype: [Debug]
+ include:
+ - os: windows-2022
+ triplet: x64-windows
+ packages: >
+ sccache
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
diff --git a/.github/workflows/build-windows-solution.yml b/.github/workflows/build-windows-solution.yml
index 1189acec818..58dcf97f159 100644
--- a/.github/workflows/build-windows-solution.yml
+++ b/.github/workflows/build-windows-solution.yml
@@ -6,26 +6,27 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- - 'src/**'
+ - "src/**"
+ merge_group:
push:
paths:
- - 'src/**'
+ - "src/**"
branches:
- main
env:
CMAKE_BUILD_PARALLEL_LEVEL: 2
- MAKEFLAGS: '-j 2'
+ MAKEFLAGS: "-j 2"
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
job:
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
diff --git a/.github/workflows/clang-lint-dummy.yml b/.github/workflows/clang-lint-dummy.yml
new file mode 100644
index 00000000000..41d488b4d0b
--- /dev/null
+++ b/.github/workflows/clang-lint-dummy.yml
@@ -0,0 +1,12 @@
+---
+name: Clang-format (dummy)
+on:
+ pull_request:
+ paths-ignore:
+ - "src/**"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
diff --git a/.github/workflows/clang-lint.yml b/.github/workflows/clang-lint.yml
index 2fe9166c4f5..67e6427d853 100644
--- a/.github/workflows/clang-lint.yml
+++ b/.github/workflows/clang-lint.yml
@@ -3,19 +3,20 @@ name: Clang-format
on:
pull_request:
paths:
- - 'src/**'
+ - "src/**"
+ merge_group:
push:
paths:
- - 'src/**'
+ - "src/**"
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
build:
runs-on: ubuntu-latest
diff --git a/.github/workflows/cron-stale.yml b/.github/workflows/cron-stale.yml
index 03d1868c449..3c68d50a247 100644
--- a/.github/workflows/cron-stale.yml
+++ b/.github/workflows/cron-stale.yml
@@ -1,18 +1,18 @@
---
-name: 'Cron - Stale issues and PRs'
+name: "Cron - Stale issues and PRs"
on:
schedule:
- - cron: '30 1 * * *'
+ - cron: "30 1 * * *"
jobs:
cancel-runs:
- if: github.event_name == 'pull_request'
+ if: github.event_name == 'pull_request' && github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- - name: Cancel Previous Runs
- uses: styfle/cancel-workflow-action@0.9.1
- with:
- access_token: ${{ github.token }}
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@0.9.1
+ with:
+ access_token: ${{ github.token }}
stale:
runs-on: ubuntu-latest
@@ -20,8 +20,8 @@ jobs:
- uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- stale-issue-message: 'This issue is stale because it has been open 120 days with no activity.'
- stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.'
+ stale-issue-message: "This issue is stale because it has been open 120 days with no activity."
+ stale-pr-message: "This PR is stale because it has been open 45 days with no activity."
days-before-issue-stale: 90
days-before-pr-stale: 30
days-before-issue-close: -1
diff --git a/.github/workflows/lua-format-dummy.yml b/.github/workflows/lua-format-dummy.yml
new file mode 100644
index 00000000000..9a52797a3ca
--- /dev/null
+++ b/.github/workflows/lua-format-dummy.yml
@@ -0,0 +1,13 @@
+---
+name: Lua-format (dumy)
+
+on:
+ pull_request:
+ paths-ignore:
+ - "data*/**"
+
+jobs:
+ lua-formatter:
+ runs-on: ubuntu-latest
+ steps:
+ - run: echo "This is a dummy job to satisfy branch protection checks"
diff --git a/.github/workflows/lua-format.yml b/.github/workflows/lua-format.yml
index 57caa336668..5a9a644e7aa 100644
--- a/.github/workflows/lua-format.yml
+++ b/.github/workflows/lua-format.yml
@@ -3,10 +3,11 @@ name: Lua-format
on:
pull_request:
paths:
- - 'data*/**'
+ - "data*/**"
+ merge_group:
push:
paths:
- - 'data*/**'
+ - "data*/**"
jobs:
lua-formatter:
runs-on: ubuntu-latest
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 066aaa23595..345a0e6f089 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -1,5 +1,5 @@
---
-name: 'PR - Labeler'
+name: "PR - Labeler"
on:
- pull_request_target
@@ -9,4 +9,4 @@ jobs:
steps:
- uses: actions/labeler@main
with:
- repo-token: '${{ secrets.GITHUB_TOKEN }}'
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/tests-lua.yml b/.github/workflows/tests-lua.yml
index ebc52323ad2..6455c5705de 100644
--- a/.github/workflows/tests-lua.yml
+++ b/.github/workflows/tests-lua.yml
@@ -3,6 +3,7 @@ name: Tests - Lua
on:
pull_request:
+ merge_group:
push:
branches:
- main
diff --git a/.gitignore b/.gitignore
index 9a9fc68fab6..aae56bd35a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -382,6 +382,7 @@ client_assertions.txt
*-house.xml
*-monster.xml
*-npc.xml
+monster_count.txt
# SFTP for Sublime
sftp-config.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5e3cbb97c28..8730b5057fb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -81,17 +81,30 @@ endif()
# === IPO ===
option(OPTIONS_ENABLE_IPO "Check and Enable interprocedural optimization (IPO/LTO)" ON)
if(OPTIONS_ENABLE_IPO)
- log_option_enabled("ipo")
-
- include(CheckIPOSupported)
- check_ipo_supported(RESULT result OUTPUT output)
- if(result)
- set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
- else()
- log_war("IPO is not supported: ${output}")
- endif()
+ log_option_enabled("IPO/LTO")
+ if(MSVC)
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /GL")
+ set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /LTCG")
+ else()
+ if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" OR CMAKE_BUILD_TYPE STREQUAL "Release")
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT result OUTPUT output)
+ if(result)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto")
+ message(STATUS "IPO/LTO enabled with -flto=auto for non-MSVC compiler.")
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+ else()
+ log_war("IPO/LTO not supported: ${output}")
+ endif()
+ else()
+ log_option_disabled("IPO/LTO")
+ endif ()
+ endif()
else()
- log_option_disabled("ipo")
+ log_option_disabled("IPO/LTO")
endif()
option(BUILD_TESTS "Build tests" OFF) # By default, tests will not be built
diff --git a/cmake/modules/CanaryLib.cmake b/cmake/modules/CanaryLib.cmake
index d5fdd782b1f..957a83c7a3e 100644
--- a/cmake/modules/CanaryLib.cmake
+++ b/cmake/modules/CanaryLib.cmake
@@ -44,11 +44,23 @@ if (CMAKE_COMPILER_IS_GNUCXX)
endif()
# === IPO ===
-check_ipo_supported(RESULT result OUTPUT output)
-if(result)
- set_property(TARGET ${PROJECT_NAME}_lib PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
+if(MSVC)
+ target_compile_options(${PROJECT_NAME}_lib PRIVATE "/GL")
+ set_target_properties(${PROJECT_NAME}_lib PROPERTIES
+ STATIC_LINKER_FLAGS "/LTCG"
+ SHARED_LINKER_FLAGS "/LTCG"
+ MODULE_LINKER_FLAGS "/LTCG"
+ EXE_LINKER_FLAGS "/LTCG")
else()
- message(WARNING "IPO is not supported: ${output}")
+ include(CheckIPOSupported)
+ check_ipo_supported(RESULT result)
+ if(result)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=auto")
+ message(STATUS "IPO/LTO enabled with -flto=auto for non-MSVC compiler.")
+ set_property(TARGET ${PROJECT_NAME}_lib PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
+ else()
+ message(WARNING "IPO/LTO is not supported: ${output}")
+ endif()
endif()
# === UNITY BUILD (compile time reducer) ===
diff --git a/config.lua.dist b/config.lua.dist
index fec7fb25e72..7471d5cd768 100644
--- a/config.lua.dist
+++ b/config.lua.dist
@@ -147,6 +147,7 @@ hazardCriticalInterval = 2000
hazardCriticalChance = 750
hazardCriticalMultiplier = 25
hazardDamageMultiplier = 200
+hazardDefenseMultiplier = 0
hazardDodgeMultiplier = 85
hazardPodsDropMultiplier = 87
hazardPodsTimeToDamage = 2000
@@ -373,6 +374,7 @@ showScriptsLogInConsole = false
criticalChance = 10
inventoryGlowOnFiveBless = false
adventurersBlessingLevel = 21
+skulledDeathLoseStoreItem = false
experienceDisplayRates = true
-- configure attack base on Fist Fighting skill/experience
-- multiplierSpeedOnFist * 5 (multiplies the value obtained from the player fist skill and multiplies it * 5) max 25 is recommended due minTicks limits else player stop attack
@@ -380,6 +382,12 @@ experienceDisplayRates = true
toggleAttackSpeedOnFist = false
multiplierSpeedOnFist = 5
maxSpeedOnFist = 500
+disableLegacyRaids = false -- disable legacy XML raids
+disableMonsterArmor = false
+combatChainDelay = 50 -- minimum: 50 miliseconds
+minElementalResistance = -200
+maxElementalResistance = 200
+maxDamageReflection = 200
-- Global server Save
-- NOTE: globalServerSaveNotifyDuration in minutes
diff --git a/data-canary/monster/demons/fury.lua b/data-canary/monster/demons/fury.lua
index f12b4ba628f..7c1946d7991 100644
--- a/data-canary/monster/demons/fury.lua
+++ b/data-canary/monster/demons/fury.lua
@@ -108,7 +108,7 @@ monster.attacks = {
-- {name ="fury skill reducer", interval = 2000, chance = 5, target = false},
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -120, maxDamage = -300, radius = 3, effect = CONST_ME_HITAREA, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -125, maxDamage = -250, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-canary/monster/magicals/guzzlemaw.lua b/data-canary/monster/magicals/guzzlemaw.lua
index 8d3902203d1..8e38d459779 100644
--- a/data-canary/monster/magicals/guzzlemaw.lua
+++ b/data-canary/monster/magicals/guzzlemaw.lua
@@ -114,7 +114,7 @@ monster.attacks = {
{ name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -900, length = 8, spread = 3, effect = CONST_ME_EXPLOSIONAREA, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 3, effect = CONST_ME_MAGIC_RED, target = false },
}
diff --git a/data-canary/scripts/spells/support/sharpshooter.lua b/data-canary/scripts/spells/support/sharpshooter.lua
index 0a419b6b07e..595c1e6db80 100644
--- a/data-canary/scripts/spells/support/sharpshooter.lua
+++ b/data-canary/scripts/spells/support/sharpshooter.lua
@@ -10,9 +10,9 @@ skill:setParameter(CONDITION_PARAM_DISABLE_DEFENSE, true)
skill:setParameter(CONDITION_PARAM_BUFF_SPELL, true)
combat:addCondition(skill)
-local speed = Condition(CONDITION_PARALYZE)
+local speed = Condition(CONDITION_HASTE)
speed:setParameter(CONDITION_PARAM_TICKS, 10000)
-speed:setFormula(-0.73, 0, -0.73, 0)
+speed:setFormula(0.7, 0, 0.7, 0)
combat:addCondition(speed)
local exhaustHealGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN)
diff --git a/data-canary/scripts/spells/support/swift_foot.lua b/data-canary/scripts/spells/support/swift_foot.lua
index 803f6980cd8..249494f38ca 100644
--- a/data-canary/scripts/spells/support/swift_foot.lua
+++ b/data-canary/scripts/spells/support/swift_foot.lua
@@ -8,7 +8,7 @@ combat:addCondition(exhaust)
local condition = Condition(CONDITION_HASTE)
condition:setParameter(CONDITION_PARAM_TICKS, 10000)
-condition:setFormula(0.8, -72, 0.8, -72)
+condition:setFormula(1.8, 72, 1.8, 72)
combat:addCondition(condition)
local exhaustAttackGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN)
diff --git a/data-otservbr-global/monster/amphibics/makara.lua b/data-otservbr-global/monster/amphibics/makara.lua
index 66bd91a17dd..a0da9573236 100644
--- a/data-otservbr-global/monster/amphibics/makara.lua
+++ b/data-otservbr-global/monster/amphibics/makara.lua
@@ -54,7 +54,7 @@ monster.flags = {
canPushCreatures = true,
staticAttackChance = 90,
targetDistance = 1,
- runHealth = 10,
+ runHealth = 0,
healthHidden = false,
isBlockable = false,
canWalkOnEnergy = false,
@@ -71,12 +71,13 @@ monster.voices = {
interval = 5000,
chance = 10,
{ text = "waddle waddle", yell = false },
+ { text = "Nihahaha!", yell = false },
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 13 },
+ { name = "platinum coin", chance = 100000, maxCount = 18 },
{ name = "makara tongue", chance = 10160 },
- { name = "makara fin", chance = 7420 },
+ { name = "makara fin", chance = 7420, maxCount = 2 },
{ name = "meat", chance = 7030, maxCount = 2 },
{ name = "cyan crystal fragment", chance = 4300 },
{ name = "yellow gem", chance = 4100 },
@@ -90,10 +91,10 @@ monster.loot = {
monster.attacks = {
{ name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -145, maxDamage = -390, target = true }, -- basic_attack
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 3, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- stone_shower_ball
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 5, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- great_stone_shower_ball
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_ICEDAMAGE, minDamage = -360, maxDamage = -390, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, -- ice_strike
- { name = "makarawatersplash", interval = 2000, chance = 25, minDamage = -380, maxDamage = -455, target = false }, -- short_water_cone-wave
+ { name = "combat", interval = 2500, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 3, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- stone_shower_ball
+ { name = "combat", interval = 3000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -305, maxDamage = -390, radius = 5, effect = CONST_ME_STONES, shootEffect = CONST_ANI_EARTH, target = true }, -- great_stone_shower_ball
+ { name = "combat", interval = 3500, chance = 20, type = COMBAT_ICEDAMAGE, minDamage = -360, maxDamage = -390, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true }, -- ice_strike
+ { name = "makarawatersplash", interval = 4000, chance = 25, minDamage = -380, maxDamage = -455, target = false }, -- short_water_cone-wave
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/aquatics/deathling_scout.lua b/data-otservbr-global/monster/aquatics/deathling_scout.lua
index d0a54d6b17a..23b959b41fe 100644
--- a/data-otservbr-global/monster/aquatics/deathling_scout.lua
+++ b/data-otservbr-global/monster/aquatics/deathling_scout.lua
@@ -34,7 +34,7 @@ monster.speed = 155
monster.manaCost = 0
monster.faction = FACTION_DEATHLING
-monster.enemyFactions = { FACTION_DEEPLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEEPLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua b/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua
index 68d5df39ea4..a7fdf805bc8 100644
--- a/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua
+++ b/data-otservbr-global/monster/aquatics/deathling_spellsinger.lua
@@ -34,7 +34,7 @@ monster.speed = 155
monster.manaCost = 0
monster.faction = FACTION_DEATHLING
-monster.enemyFactions = { FACTION_DEEPLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEEPLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_brawler.lua b/data-otservbr-global/monster/aquatics/deepling_brawler.lua
index ed81778f449..bc44da075f9 100644
--- a/data-otservbr-global/monster/aquatics/deepling_brawler.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_brawler.lua
@@ -34,7 +34,7 @@ monster.speed = 85
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_elite.lua b/data-otservbr-global/monster/aquatics/deepling_elite.lua
index a80ee5575cb..c990f4b0447 100644
--- a/data-otservbr-global/monster/aquatics/deepling_elite.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_elite.lua
@@ -34,7 +34,7 @@ monster.speed = 165
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_guard.lua b/data-otservbr-global/monster/aquatics/deepling_guard.lua
index c77609e5964..23f87f55459 100644
--- a/data-otservbr-global/monster/aquatics/deepling_guard.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_guard.lua
@@ -35,7 +35,7 @@ monster.speed = 135
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_scout.lua b/data-otservbr-global/monster/aquatics/deepling_scout.lua
index d7ce0c2db19..dadb1d8a19a 100644
--- a/data-otservbr-global/monster/aquatics/deepling_scout.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_scout.lua
@@ -34,7 +34,7 @@ monster.speed = 65
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua b/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua
index 56557793784..15efdb2cf87 100644
--- a/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_spellsinger.lua
@@ -34,7 +34,7 @@ monster.speed = 95
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_tyrant.lua b/data-otservbr-global/monster/aquatics/deepling_tyrant.lua
index 290aeee78d2..59a2a2ca79d 100644
--- a/data-otservbr-global/monster/aquatics/deepling_tyrant.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_tyrant.lua
@@ -34,7 +34,7 @@ monster.speed = 155
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_warrior.lua b/data-otservbr-global/monster/aquatics/deepling_warrior.lua
index 7f31c9ae504..1da00f6c2a7 100644
--- a/data-otservbr-global/monster/aquatics/deepling_warrior.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_warrior.lua
@@ -34,7 +34,7 @@ monster.speed = 145
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/aquatics/deepling_worker.lua b/data-otservbr-global/monster/aquatics/deepling_worker.lua
index cb958c11553..395f4ed57be 100644
--- a/data-otservbr-global/monster/aquatics/deepling_worker.lua
+++ b/data-otservbr-global/monster/aquatics/deepling_worker.lua
@@ -34,7 +34,7 @@ monster.speed = 65
monster.manaCost = 0
monster.faction = FACTION_DEEPLING
-monster.enemyFactions = { FACTION_DEATHLING, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_DEATHLING }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/birds/dire_penguin.lua b/data-otservbr-global/monster/birds/dire_penguin.lua
index 6eb73eff344..d0b70ec954a 100644
--- a/data-otservbr-global/monster/birds/dire_penguin.lua
+++ b/data-otservbr-global/monster/birds/dire_penguin.lua
@@ -17,10 +17,10 @@ monster.raceId = 335
monster.Bestiary = {
class = "Bird",
race = BESTY_RACE_BIRD,
- toKill = 500,
- FirstUnlock = 25,
- SecondUnlock = 250,
- CharmsPoints = 15,
+ toKill = 5,
+ FirstUnlock = 1,
+ SecondUnlock = 3,
+ CharmsPoints = 30,
Stars = 2,
Occurrence = 3,
Locations = "Any place with penguins like, Formorgar Glacier, Helheim, Tyrsung or Svargrond. \z
diff --git a/data-otservbr-global/monster/bosses/alchemist_container.lua b/data-otservbr-global/monster/bosses/alchemist_container.lua
new file mode 100644
index 00000000000..62905d5975e
--- /dev/null
+++ b/data-otservbr-global/monster/bosses/alchemist_container.lua
@@ -0,0 +1,96 @@
+local mType = Game.createMonsterType("Alchemist Container")
+local monster = {}
+
+monster.description = "Alchemist Container"
+monster.experience = 0
+monster.outfit = {
+ lookTypeEx = 39952,
+}
+
+monster.health = 1200
+monster.maxHealth = 1200
+monster.race = "undead"
+monster.corpse = 39949
+monster.speed = 0
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 15,
+}
+
+monster.strategiesTarget = {
+ nearest = 60,
+ health = 30,
+ damage = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ critChance = 10,
+ staticAttackChance = 90,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.attacks = {}
+
+monster.defenses = {
+ defense = 54,
+ armor = 59,
+ mitigation = 3.7,
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 0 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType.onThink = function(monster, interval) end
+
+mType.onAppear = function(monster, creature)
+ if monster:getType():isRewardBoss() then
+ monster:setReward(true)
+ end
+end
+
+mType.onDisappear = function(monster, creature) end
+
+mType.onMove = function(monster, creature, fromPosition, toPosition) end
+
+mType.onSay = function(monster, creature, type, message) end
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/bosses/antenna.lua b/data-otservbr-global/monster/bosses/antenna.lua
new file mode 100644
index 00000000000..f1a165177cc
--- /dev/null
+++ b/data-otservbr-global/monster/bosses/antenna.lua
@@ -0,0 +1,96 @@
+local mType = Game.createMonsterType("Antenna")
+local monster = {}
+
+monster.description = "Antenna"
+monster.experience = 0
+monster.outfit = {
+ lookTypeEx = 850,
+}
+
+monster.health = 5000
+monster.maxHealth = 5000
+monster.race = "undead"
+monster.corpse = 0
+monster.speed = 0
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 15,
+}
+
+monster.strategiesTarget = {
+ nearest = 60,
+ health = 30,
+ damage = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ critChance = 10,
+ staticAttackChance = 90,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.attacks = {}
+
+monster.defenses = {
+ defense = 54,
+ armor = 59,
+ mitigation = 3.7,
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 0 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType.onThink = function(monster, interval) end
+
+mType.onAppear = function(monster, creature)
+ if monster:getType():isRewardBoss() then
+ monster:setReward(true)
+ end
+end
+
+mType.onDisappear = function(monster, creature) end
+
+mType.onMove = function(monster, creature, fromPosition, toPosition) end
+
+mType.onSay = function(monster, creature, type, message) end
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/bosses/cerebellum.lua b/data-otservbr-global/monster/bosses/cerebellum.lua
new file mode 100644
index 00000000000..b83fcec8615
--- /dev/null
+++ b/data-otservbr-global/monster/bosses/cerebellum.lua
@@ -0,0 +1,94 @@
+local mType = Game.createMonsterType("Cerebellum")
+local monster = {}
+
+monster.description = "Cerebellum"
+monster.experience = 0
+monster.outfit = {
+ lookTypeEx = 32572,
+}
+
+monster.health = 20000
+monster.maxHealth = monster.health
+monster.race = "undead"
+monster.corpse = 32576
+monster.speed = 0
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 90,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.loot = {}
+
+monster.attacks = {
+ { name = "combat", interval = 2000, chance = 50, type = COMBAT_ENERGYDAMAGE, minDamage = -90, maxDamage = -180, range = 7, shootEffect = CONST_ANI_ENERGY, target = true },
+ { name = "heal brain head", interval = 2000, chance = 10, target = false },
+}
+
+monster.defenses = {
+ defense = 78,
+ armor = 78,
+ mitigation = 3.27,
+ { name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2000, minDamage = 50, maxDamage = 200, effect = CONST_ME_MAGIC_BLUE },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = -30 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "invisible", condition = true },
+}
+
+monster.voices = {
+ interval = 5000,
+ chance = 10,
+ { text = "Feel the power of death unleashed!", yell = false },
+ { text = "I will rule again and my realm of death will span the world!", yell = false },
+ { text = "My lich-knights will conquer this world for me!", yell = false },
+}
+
+mType.onThink = function(monster, interval) end
+
+mType.onAppear = function(monster, creature)
+ if monster:getType():isRewardBoss() then
+ monster:setReward(true)
+ end
+end
+
+mType.onDisappear = function(monster, creature) end
+
+mType.onMove = function(monster, creature, fromPosition, toPosition) end
+
+mType.onSay = function(monster, creature, type, message) end
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/bosses/doctor_marrow.lua b/data-otservbr-global/monster/bosses/doctor_marrow.lua
index 308ab14b27b..cccdd2e4d57 100644
--- a/data-otservbr-global/monster/bosses/doctor_marrow.lua
+++ b/data-otservbr-global/monster/bosses/doctor_marrow.lua
@@ -22,7 +22,7 @@ monster.manaCost = 0
monster.changeTarget = {
interval = 4000,
- chance = 15,
+ chance = 10,
}
monster.strategiesTarget = {
@@ -65,12 +65,17 @@ monster.voices = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 },
+ { name = "combat", interval = 3000, chance = 20, type = COMBAT_LIFEDRAIN, minDamage = -50, maxDamage = -2800, effect = CONST_ME_MAGIC_RED, target = false, radius = 3 },
+ { name = "doctor marrow explosion", interval = 10000, chance = 25, target = true, range = 1 },
+ { name = "root", interval = 4000, chance = 10, target = true },
+ { name = "fear", interval = 3500, chance = 10, target = true },
}
monster.defenses = {
defense = 54,
armor = 59,
mitigation = 3.7,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 900, maxDamage = 2400, effect = CONST_ME_MAGIC_BLUE, target = false },
}
monster.elements = {
diff --git a/data-otservbr-global/monster/bosses/the_abomination.lua b/data-otservbr-global/monster/bosses/the_abomination.lua
index e6ba0b1ae06..5d0c02c4b14 100644
--- a/data-otservbr-global/monster/bosses/the_abomination.lua
+++ b/data-otservbr-global/monster/bosses/the_abomination.lua
@@ -92,7 +92,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, skill = 90, attack = 120 },
- { name = "speed", interval = 1000, chance = 12, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 },
+ { name = "speed", interval = 1000, chance = 12, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 },
{ name = "combat", interval = 1000, chance = 9, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -650, radius = 4, effect = CONST_ME_POISONAREA, target = false },
{ name = "combat", interval = 1000, chance = 11, type = COMBAT_LIFEDRAIN, minDamage = -400, maxDamage = -900, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_SOUND_GREEN, target = true },
{ name = "combat", interval = 2000, chance = 19, type = COMBAT_PHYSICALDAMAGE, minDamage = -350, maxDamage = -850, length = 7, spread = 0, shootEffect = CONST_ANI_POISON, effect = CONST_ME_HITBYPOISON, target = false },
diff --git a/data-otservbr-global/monster/bosses/the_collector.lua b/data-otservbr-global/monster/bosses/the_collector.lua
index b3fa70f85c6..b63f6c8ace3 100644
--- a/data-otservbr-global/monster/bosses/the_collector.lua
+++ b/data-otservbr-global/monster/bosses/the_collector.lua
@@ -67,7 +67,7 @@ monster.loot = {}
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, skill = 100, attack = 40 },
- { name = "speed", interval = 1000, chance = 13, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 },
+ { name = "speed", interval = 1000, chance = 13, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 },
{ name = "combat", interval = 1000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -85, range = 7, shootEffect = CONST_ANI_LARGEROCK, target = false },
{ name = "melee", interval = 2000, chance = 15, minDamage = -10, maxDamage = -80 },
}
diff --git a/data-otservbr-global/monster/bosses/the_monster.lua b/data-otservbr-global/monster/bosses/the_monster.lua
new file mode 100644
index 00000000000..c7f84935b20
--- /dev/null
+++ b/data-otservbr-global/monster/bosses/the_monster.lua
@@ -0,0 +1,133 @@
+local mType = Game.createMonsterType("The Monster")
+local monster = {}
+
+monster.description = "The Monster"
+monster.experience = 30000
+monster.outfit = {
+ lookType = 1600,
+}
+
+monster.bosstiary = {
+ bossRaceId = 2299,
+ bossRace = RARITY_ARCHFOE,
+}
+
+monster.health = 450000
+monster.maxHealth = 450000
+monster.race = "blood"
+monster.corpse = 42247
+monster.speed = 180
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 15,
+}
+
+monster.strategiesTarget = {
+ nearest = 60,
+ health = 30,
+ damage = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = true,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ critChance = 10,
+ staticAttackChance = 90,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.loot = {
+ { name = "platinum coin", chance = 100000, maxcount = 30 },
+ { id = 3039, chance = 35542, maxCount = 2 }, -- red gem
+ { name = "ultimate health potion", chance = 27000, maxcount = 7 },
+ { name = "ultimate mana potion", chance = 24300, maxcount = 5 },
+ { name = "ultimate spirit potion", chance = 25750, maxcount = 4 },
+ { name = "mastermind potion", chance = 23200, maxcount = 3 },
+ { name = "berserk potion", chance = 24800, maxcount = 3 },
+ { name = "bullseye potion", chance = 23500, maxcount = 3 },
+ { name = "yellow gem", chance = 26200, maxcount = 5 },
+ { name = "blue gem", chance = 25100 },
+ { name = "green gem", chance = 24600 },
+ { name = "violet gem", chance = 25350 },
+ { name = "giant amethyst", chance = 4300 },
+ { name = "giant topaz", chance = 4600 },
+ { name = "giant emerald", chance = 4500 },
+ { id = 33778, chance = 900 }, -- raw watermelon turmaline
+ { name = "alchemist's notepad", chance = 420 },
+ { name = "antler-horn helmet", chance = 390 },
+ { name = "mutant bone kilt", chance = 450 },
+ { name = "mutated skin armor", chance = 430 },
+ { name = "mutated skin legs", chance = 410 },
+ { name = "stitched mutant hide legs", chance = 440 },
+ { name = "alchemist's boots", chance = 460 },
+ { name = "mutant bone boots", chance = 400 },
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -2800 },
+ { name = "combat", interval = 2000, chance = 35, type = COMBAT_ENERGYDAMAGE, minDamage = -600, maxDamage = -1200, effect = CONST_ME_ENERGYAREA, target = true, radius = 5, range = 3 },
+ { name = "destroy magic walls", interval = 1000, chance = 50 },
+}
+
+monster.defenses = {
+ defense = 54,
+ armor = 59,
+ mitigation = 3.7,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 900, maxDamage = 2400, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 0 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType.onThink = function(monster, interval) end
+
+mType.onAppear = function(monster, creature)
+ if monster:getType():isRewardBoss() then
+ monster:setReward(true)
+ end
+end
+
+mType.onDisappear = function(monster, creature) end
+
+mType.onMove = function(monster, creature, fromPosition, toPosition) end
+
+mType.onSay = function(monster, creature, type, message) end
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/constructs/ice_golem.lua b/data-otservbr-global/monster/constructs/ice_golem.lua
index e5c226bae5c..0ab245558c1 100644
--- a/data-otservbr-global/monster/constructs/ice_golem.lua
+++ b/data-otservbr-global/monster/constructs/ice_golem.lua
@@ -94,7 +94,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -220 },
- { name = "speed", interval = 1000, chance = 13, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 },
+ { name = "speed", interval = 1000, chance = 13, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_ENERGYHIT, target = false, duration = 20000 },
{ name = "combat", interval = 1000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -85, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = false },
{ name = "ice golem skill reducer", interval = 2000, chance = 10, target = false },
}
diff --git a/data-otservbr-global/monster/constructs/infected_weeper.lua b/data-otservbr-global/monster/constructs/infected_weeper.lua
index e302618f3eb..4bcccd5b9fa 100644
--- a/data-otservbr-global/monster/constructs/infected_weeper.lua
+++ b/data-otservbr-global/monster/constructs/infected_weeper.lua
@@ -91,7 +91,7 @@ monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -280 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -250, maxDamage = -700, length = 5, spread = 3, effect = CONST_ME_MAGIC_RED, target = false },
{ name = "combat", interval = 2000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -80, maxDamage = -250, radius = 3, effect = CONST_ME_HITBYFIRE, target = false },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/constructs/lava_golem.lua b/data-otservbr-global/monster/constructs/lava_golem.lua
index aed9ad864c0..38db564d494 100644
--- a/data-otservbr-global/monster/constructs/lava_golem.lua
+++ b/data-otservbr-global/monster/constructs/lava_golem.lua
@@ -112,7 +112,7 @@ monster.attacks = {
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -600, maxDamage = -1300, length = 8, spread = 3, effect = CONST_ME_MORTAREA, target = false },
{ name = "lava golem soulfire", interval = 2000, chance = 15, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -220, maxDamage = -350, radius = 4, effect = CONST_ME_FIREAREA, target = true },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 3, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
{ name = "combat", interval = 2000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -280, maxDamage = -350, radius = 3, effect = CONST_ME_HITBYFIRE, target = false },
}
diff --git a/data-otservbr-global/monster/constructs/magma_crawler.lua b/data-otservbr-global/monster/constructs/magma_crawler.lua
index 39c58ab8838..84ad15af13b 100644
--- a/data-otservbr-global/monster/constructs/magma_crawler.lua
+++ b/data-otservbr-global/monster/constructs/magma_crawler.lua
@@ -111,7 +111,7 @@ monster.attacks = {
{ name = "magma crawler soulfire", interval = 2000, chance = 20, target = false },
{ name = "soulfire rune", interval = 2000, chance = 10, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -140, maxDamage = -180, radius = 3, effect = CONST_ME_HITBYFIRE, target = false },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/constructs/orewalker.lua b/data-otservbr-global/monster/constructs/orewalker.lua
index 767b8a049ae..0ebf08181ff 100644
--- a/data-otservbr-global/monster/constructs/orewalker.lua
+++ b/data-otservbr-global/monster/constructs/orewalker.lua
@@ -113,7 +113,7 @@ monster.attacks = {
-- poison
{ name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -800, maxDamage = -1080, radius = 3, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true },
{ name = "drunk", interval = 2000, chance = 15, radius = 4, effect = CONST_ME_SOUND_PURPLE, target = false, duration = 6000 },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/constructs/weeper.lua b/data-otservbr-global/monster/constructs/weeper.lua
index 75a8d7ebb42..63dfa572727 100644
--- a/data-otservbr-global/monster/constructs/weeper.lua
+++ b/data-otservbr-global/monster/constructs/weeper.lua
@@ -101,7 +101,7 @@ monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -400, maxDamage = -1000, length = 8, spread = 0, effect = CONST_ME_FIREATTACK, target = false },
{ name = "combat", interval = 3000, chance = 100, type = COMBAT_FIREDAMAGE, minDamage = -80, maxDamage = -250, radius = 3, effect = CONST_ME_HITBYFIRE, target = false },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/demons/dawnfire_asura.lua b/data-otservbr-global/monster/demons/dawnfire_asura.lua
index ac6fb0d5308..a3b50cfef84 100644
--- a/data-otservbr-global/monster/demons/dawnfire_asura.lua
+++ b/data-otservbr-global/monster/demons/dawnfire_asura.lua
@@ -110,7 +110,7 @@ monster.attacks = {
{ name = "combat", interval = 3700, chance = 17, type = COMBAT_LIFEDRAIN, minDamage = -100, maxDamage = -300, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false },
{ name = "combat", interval = 3200, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -100, maxDamage = -350, radius = 4, range = 5, target = true, effect = CONST_ME_MORTAREA },
{ name = "combat", interval = 2700, chance = 20, type = COMBAT_FIREDAMAGE, minDamage = -95, maxDamage = -180, range = 3, shootEffect = CONST_ANI_FIRE, target = true },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/demons/demon.lua b/data-otservbr-global/monster/demons/demon.lua
index 3a1208c210b..0839ae00b41 100644
--- a/data-otservbr-global/monster/demons/demon.lua
+++ b/data-otservbr-global/monster/demons/demon.lua
@@ -23,10 +23,7 @@ monster.Bestiary = {
CharmsPoints = 50,
Stars = 4,
Occurrence = 0,
- Locations = "Hero Cave, Ferumbras' Citadel, Goroma, Ghostlands Warlock area unreachable, \z
- Liberty Bay hidden underground passage unreachable, Razachai, deep in Pits of Inferno (found in every throneroom except Verminor's), \z
- deep Formorgar Mines, Demon Forge, Alchemist Quarter, Magician Quarter, Chyllfroest, Oramond Dungeon, \z
- Abandoned Sewers, Hell Hub and Halls of Ascension.",
+ Locations = "Hero Cave, Ferumbras' Citadel, Goroma, Ghostlands Warlock area unreachable, Liberty Bay hidden underground passage unreachable, Razachai, deep in Pits of Inferno (found in every throneroom except Verminor's), deep Formorgar Mines, Demon Forge, Alchemist Quarter, Magician Quarter, Chyllfroest, Oramond Dungeon, Abandoned Sewers, Hell Hub and Halls of Ascension.",
}
monster.health = 8200
diff --git a/data-otservbr-global/monster/demons/fury.lua b/data-otservbr-global/monster/demons/fury.lua
index cdaad15cf8f..cfa187717a8 100644
--- a/data-otservbr-global/monster/demons/fury.lua
+++ b/data-otservbr-global/monster/demons/fury.lua
@@ -107,7 +107,7 @@ monster.attacks = {
{ name = "fury skill reducer", interval = 2000, chance = 5, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -120, maxDamage = -300, radius = 3, effect = CONST_ME_HITAREA, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -125, maxDamage = -250, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/demons/midnight_asura.lua b/data-otservbr-global/monster/demons/midnight_asura.lua
index c3b0b3791e6..f73ceb8e519 100644
--- a/data-otservbr-global/monster/demons/midnight_asura.lua
+++ b/data-otservbr-global/monster/demons/midnight_asura.lua
@@ -118,7 +118,7 @@ monster.attacks = {
{ name = "combat", interval = 4100, chance = 27, type = COMBAT_DEATHDAMAGE, minDamage = -150, maxDamage = -300, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false },
{ name = "combat", interval = 2700, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -50, maxDamage = -200, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
{ name = "combat", interval = 3100, chance = 15, type = COMBAT_ENERGYDAMAGE, minDamage = -50, maxDamage = -100, range = 1, shootEffect = CONST_ANI_ENERGY, target = true },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/demons/plaguesmith.lua b/data-otservbr-global/monster/demons/plaguesmith.lua
index 6fbc96b533d..28470a0500a 100644
--- a/data-otservbr-global/monster/demons/plaguesmith.lua
+++ b/data-otservbr-global/monster/demons/plaguesmith.lua
@@ -115,7 +115,7 @@ monster.attacks = {
{ name = "melee", interval = 1500, chance = 100, minDamage = 0, maxDamage = -539, condition = { type = CONDITION_POISON, totalDamage = 200, interval = 4000 } },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -60, maxDamage = -114, radius = 4, effect = CONST_ME_POISONAREA, target = false },
{ name = "plaguesmith wave", interval = 2000, chance = 10, minDamage = -100, maxDamage = -350, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 4, effect = CONST_ME_POISONAREA, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 4, effect = CONST_ME_POISONAREA, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/dragons/ghastly_dragon.lua b/data-otservbr-global/monster/dragons/ghastly_dragon.lua
index a4e989ea21f..11ade52fea9 100644
--- a/data-otservbr-global/monster/dragons/ghastly_dragon.lua
+++ b/data-otservbr-global/monster/dragons/ghastly_dragon.lua
@@ -115,7 +115,7 @@ monster.attacks = {
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -80, maxDamage = -230, range = 7, effect = CONST_ME_MAGIC_RED, target = true },
{ name = "ghastly dragon wave", interval = 2000, chance = 10, minDamage = -120, maxDamage = -250, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -110, maxDamage = -180, radius = 4, effect = CONST_ME_MORTAREA, target = false },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/elementals/ironblight.lua b/data-otservbr-global/monster/elementals/ironblight.lua
index 593069318ed..a7fba4ab036 100644
--- a/data-otservbr-global/monster/elementals/ironblight.lua
+++ b/data-otservbr-global/monster/elementals/ironblight.lua
@@ -110,7 +110,7 @@ monster.attacks = {
{ name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -460, maxDamage = -480, radius = 6, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -260, maxDamage = -350, length = 7, spread = 0, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEATTACK, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -180, maxDamage = -250, radius = 2, shootEffect = CONST_ANI_GREENSTAR, effect = CONST_ME_BIGPLANTS, target = true },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, length = 5, spread = 0, effect = CONST_ME_BLOCKHIT, target = false, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/giants/ogre_brute.lua b/data-otservbr-global/monster/giants/ogre_brute.lua
index 26272cc0716..10f479ce379 100644
--- a/data-otservbr-global/monster/giants/ogre_brute.lua
+++ b/data-otservbr-global/monster/giants/ogre_brute.lua
@@ -95,7 +95,7 @@ monster.loot = {
{ id = 7428, chance = 500 }, -- bonebreaker
{ id = 22171, chance = 800 }, -- ogre klubba
{ id = 3465, chance = 500 }, -- pot
- { id = 8906, chance = 200 }, -- heavily rusted helmet
+ { name = "rusted helmet", chance = 220 },
{ id = 22192, chance = 300 }, -- shamanic mask
}
diff --git a/data-otservbr-global/monster/giants/ogre_rowdy.lua b/data-otservbr-global/monster/giants/ogre_rowdy.lua
index c479a8f9c7b..70b45bedd2e 100644
--- a/data-otservbr-global/monster/giants/ogre_rowdy.lua
+++ b/data-otservbr-global/monster/giants/ogre_rowdy.lua
@@ -34,7 +34,7 @@ monster.speed = 210
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/giants/ogre_ruffian.lua b/data-otservbr-global/monster/giants/ogre_ruffian.lua
index fd9ab8232f1..aa561883463 100644
--- a/data-otservbr-global/monster/giants/ogre_ruffian.lua
+++ b/data-otservbr-global/monster/giants/ogre_ruffian.lua
@@ -34,7 +34,7 @@ monster.speed = 215
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/giants/ogre_sage.lua b/data-otservbr-global/monster/giants/ogre_sage.lua
index 83bb6ce0b71..5f619a75ce7 100644
--- a/data-otservbr-global/monster/giants/ogre_sage.lua
+++ b/data-otservbr-global/monster/giants/ogre_sage.lua
@@ -34,7 +34,7 @@ monster.speed = 230
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua b/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua
index 03c8a6000c3..8e269d760c4 100644
--- a/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua
+++ b/data-otservbr-global/monster/humanoids/crazed_summer_rearguard.lua
@@ -98,11 +98,9 @@ monster.loot = {
}
monster.attacks = {
- { name = "melee", interval = 2000, chance = 100, minDamage = -210, maxDamage = -530 },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -270, maxDamage = -710, length = 3, spread = 0, effect = CONST_ME_FIREAREA, target = false },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -250, maxDamage = -300, range = 7, shootEffect = CONST_ANI_FIRE, target = false },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -350, maxDamage = -380, radius = 5, effect = CONST_ME_EXPLOSIONHIT, target = true },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_FIREDAMAGE, minDamage = -200, maxDamage = -350, radius = 5, effect = CONST_ME_EXPLOSIONAREA, target = true },
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 },
+ { name = "combat", interval = 2500, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -150, maxDamage = -300, range = 6, effect = CONST_ME_FIREATTACK, target = true },
+ { name = "combat", interval = 3000, chance = 30, type = COMBAT_FIREDAMAGE, minDamage = -200, maxDamage = -300, range = 6, radius = 2, effect = CONST_ME_FIREAREA, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua b/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua
index 9a9770186f3..c614fc2cbbc 100644
--- a/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua
+++ b/data-otservbr-global/monster/humanoids/dworc_voodoomaster.lua
@@ -93,7 +93,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -20 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -40, range = 1, effect = CONST_ME_MAGIC_RED, target = false },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
{ name = "drunk", interval = 2000, chance = 10, range = 7, shootEffect = CONST_ANI_ENERGY, effect = CONST_ME_TELEPORT, target = false },
{ name = "outfit", interval = 2000, chance = 10, range = 7, effect = CONST_ME_MAGIC_BLUE, target = false, duration = 5000, outfitMonster = "chicken" },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -6, maxDamage = -18, radius = 6, effect = CONST_ME_GREEN_RINGS, target = false },
diff --git a/data-otservbr-global/monster/humanoids/lost_berserker.lua b/data-otservbr-global/monster/humanoids/lost_berserker.lua
index cd48787df95..fb161e8cd63 100644
--- a/data-otservbr-global/monster/humanoids/lost_berserker.lua
+++ b/data-otservbr-global/monster/humanoids/lost_berserker.lua
@@ -112,7 +112,7 @@ monster.attacks = {
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -300, range = 7, shootEffect = CONST_ANI_WHIRLWINDAXE, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -250, range = 7, radius = 3, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_EXPLOSIONAREA, target = true },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -150, maxDamage = -250, radius = 5, effect = CONST_ME_MAGIC_RED, target = false },
- { name = "speed", interval = 2000, chance = 10, speedChange = -800, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
+ { name = "speed", interval = 2000, chance = 10, speedChange = -100, radius = 2, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
{ name = "drunk", interval = 2000, chance = 10, radius = 4, effect = CONST_ME_STUN, target = true, duration = 6000 },
}
diff --git a/data-otservbr-global/monster/humanoids/troll_guard.lua b/data-otservbr-global/monster/humanoids/troll_guard.lua
index a796c9226dc..67887f566c4 100644
--- a/data-otservbr-global/monster/humanoids/troll_guard.lua
+++ b/data-otservbr-global/monster/humanoids/troll_guard.lua
@@ -17,13 +17,13 @@ monster.raceId = 745
monster.Bestiary = {
class = "Humanoid",
race = BESTY_RACE_HUMANOID,
- toKill = 500,
- FirstUnlock = 25,
- SecondUnlock = 250,
- CharmsPoints = 15,
+ toKill = 5,
+ FirstUnlock = 1,
+ SecondUnlock = 3,
+ CharmsPoints = 30,
Stars = 2,
Occurrence = 3,
- Locations = "Rookgaards central cave in the Mapper Coords125.64125.136104textnew western Troll tunnel, north-west of Carlin during raids and Thais Knights Guild arena during raids on Kingsday Mini World ChangeKingsday.",
+ Locations = "Rookgaard and in Thais during raids",
}
monster.health = 60
diff --git a/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua b/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua
index fc98bcab36d..924b18fd4f0 100644
--- a/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua
+++ b/data-otservbr-global/monster/humans/black_sphinx_acolyte.lua
@@ -34,7 +34,7 @@ monster.speed = 155
monster.manaCost = 0
monster.faction = FACTION_FAFNAR
-monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/burning_gladiator.lua b/data-otservbr-global/monster/humans/burning_gladiator.lua
index 8923d0928cb..b2ba5ba4d9f 100644
--- a/data-otservbr-global/monster/humans/burning_gladiator.lua
+++ b/data-otservbr-global/monster/humans/burning_gladiator.lua
@@ -38,7 +38,7 @@ monster.speed = 145
monster.manaCost = 0
monster.faction = FACTION_FAFNAR
-monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/hardened_usurper_archer.lua b/data-otservbr-global/monster/humans/hardened_usurper_archer.lua
index cbfe82cd9eb..3da9570b2a1 100644
--- a/data-otservbr-global/monster/humans/hardened_usurper_archer.lua
+++ b/data-otservbr-global/monster/humans/hardened_usurper_archer.lua
@@ -21,7 +21,7 @@ monster.speed = 125
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/hardened_usurper_knight.lua b/data-otservbr-global/monster/humans/hardened_usurper_knight.lua
index 341b85f2de2..4ae4ae081e1 100644
--- a/data-otservbr-global/monster/humans/hardened_usurper_knight.lua
+++ b/data-otservbr-global/monster/humans/hardened_usurper_knight.lua
@@ -21,7 +21,7 @@ monster.speed = 130
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua b/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua
index 04282653df3..6b25ce5ad76 100644
--- a/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua
+++ b/data-otservbr-global/monster/humans/hardened_usurper_warlock.lua
@@ -21,7 +21,7 @@ monster.speed = 165
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua b/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua
index a97c9a3c56d..b185ccbf11d 100644
--- a/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua
+++ b/data-otservbr-global/monster/humans/priestess_of_the_wild_sun.lua
@@ -38,7 +38,7 @@ monster.speed = 160
monster.manaCost = 0
monster.faction = FACTION_FAFNAR
-monster.enemyFactions = { FACTION_ANUMA, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_ANUMA }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/usurper_archer.lua b/data-otservbr-global/monster/humans/usurper_archer.lua
index 4f01920b395..88a4b3782f7 100644
--- a/data-otservbr-global/monster/humans/usurper_archer.lua
+++ b/data-otservbr-global/monster/humans/usurper_archer.lua
@@ -34,7 +34,7 @@ monster.speed = 125
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/usurper_knight.lua b/data-otservbr-global/monster/humans/usurper_knight.lua
index 7116b67fbfa..aada0828995 100644
--- a/data-otservbr-global/monster/humans/usurper_knight.lua
+++ b/data-otservbr-global/monster/humans/usurper_knight.lua
@@ -34,7 +34,7 @@ monster.speed = 130
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/humans/usurper_warlock.lua b/data-otservbr-global/monster/humans/usurper_warlock.lua
index 86f582d8e2c..8a3672a9d90 100644
--- a/data-otservbr-global/monster/humans/usurper_warlock.lua
+++ b/data-otservbr-global/monster/humans/usurper_warlock.lua
@@ -34,7 +34,7 @@ monster.speed = 165
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua b/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua
new file mode 100644
index 00000000000..b0e6c74131f
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/cunning_werepanther.lua
@@ -0,0 +1,131 @@
+local mType = Game.createMonsterType("Cunning Werepanther")
+local monster = {}
+
+monster.description = "a cunning werepanther"
+monster.experience = 3620
+monster.outfit = {
+ lookType = 1648,
+ lookHead = 18,
+ lookBody = 124,
+ lookLegs = 74,
+ lookFeet = 81,
+ lookAddons = 1,
+ lookMount = 0,
+}
+
+monster.raceId = 2403
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns, Oskayaat",
+}
+
+monster.health = 4300
+monster.maxHealth = 4300
+monster.race = "blood"
+monster.corpse = 43959
+monster.speed = 125
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {
+ interval = 5000,
+ chance = 10,
+ { text = "Grrr", yell = false },
+}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 80 },
+ { name = "platinum coin", chance = 100000, maxCount = 11 },
+ { name = "werepanther claw", chance = 12780 },
+ { name = "golden sickle", chance = 5120 },
+ { name = "meat", chance = 5500, maxCount = 2 },
+ { name = "small topaz", chance = 7120, maxCount = 4 },
+ { name = "moonlight crystals", chance = 2550 },
+ { id = 3037, chance = 5130 }, -- yellow gem
+ { name = "lightning headband", chance = 7200 },
+ { name = "ripper lance", chance = 850 },
+ { name = "gemmed figurine", chance = 1770 },
+ { id = 816, chance = 4710 }, -- lightning pendant
+ { name = "fur armor", chance = 2620 },
+ { id = 43917, chance = 600 }, -- werepanther trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -75, maxDamage = -540 },
+ { name = "combat", interval = 3300, chance = 20, type = COMBAT_ENERGYDAMAGE, minDamage = -175, maxDamage = -350, radius = 2, effect = CONST_ME_ENERGYAREA, shootEffect = CONST_ANI_ENERGY, range = 4, target = true },
+ { name = "combat", interval = 2300, chance = 15, type = COMBAT_ENERGYDAMAGE, minDamage = -250, maxDamage = -425, radius = 3, effect = CONST_ME_ENERGYAREA, target = false },
+ { name = "combat", interval = 2700, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -225, maxDamage = -350, radius = 2, effect = CONST_ME_YELLOWSMOKE, shootEffect = CONST_ANI_LARGEROCK, range = 4, target = true },
+ { name = "combat", interval = 3700, chance = 35, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -375, radius = 3, effect = CONST_ME_STONE_STORM, target = false },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 72,
+ mitigation = 2.05,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 20 },
+ { type = COMBAT_EARTHDAMAGE, percent = -25 },
+ { type = COMBAT_FIREDAMAGE, percent = -15 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 20 },
+ { type = COMBAT_HOLYDAMAGE, percent = 10 },
+ { type = COMBAT_DEATHDAMAGE, percent = -10 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua b/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua
new file mode 100644
index 00000000000..724e5bcced7
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/feral_werecrocodile.lua
@@ -0,0 +1,127 @@
+local mType = Game.createMonsterType("Feral Werecrocodile")
+local monster = {}
+
+monster.description = "a feral werecrocodile"
+monster.experience = 5430
+monster.outfit = {
+ lookType = 1647,
+ lookHead = 116,
+ lookBody = 95,
+ lookLegs = 19,
+ lookFeet = 21,
+ lookAddons = 0,
+ lookMount = 0,
+}
+
+monster.raceId = 2389
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns",
+}
+
+monster.health = 6400
+monster.maxHealth = 6400
+monster.race = "blood"
+monster.corpse = 43767
+monster.speed = 110
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 100 },
+ { name = "platinum coin", chance = 100000, maxCount = 21 },
+ { name = "werecrocodile tongue", chance = 10800 },
+ { name = "war hammer", chance = 5000 },
+ { name = "ham", chance = 5500, maxCount = 2 },
+ { name = "moonlight crystals", chance = 3000 },
+ { name = "violet gem", chance = 1370 },
+ { name = "green crystal shard", chance = 2800 },
+ { name = "ornate crossbow", chance = 680 },
+ { name = "terra mantle", chance = 2190 },
+ { name = "golden sun coin", chance = 1820 },
+ { name = "sun brooch", chance = 680 },
+ { name = "swamplair armor", chance = 230 },
+ { id = 43916, chance = 200 }, -- werecrocodile trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -50, maxDamage = -485 },
+ { name = "combat", interval = 3400, chance = 33, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -450, length = 7, spread = 3, effect = CONST_ME_MORTAREA, target = false },
+ { name = "combat", interval = 2700, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -450, radius = 3, effect = CONST_ME_MORTAREA, shootEffect = CONST_ANI_DEATH, range = 4, target = true },
+ { name = "combat", interval = 3300, chance = 30, type = COMBAT_LIFEDRAIN, minDamage = -175, maxDamage = -350, radius = 1, effect = CONST_ME_MAGIC_RED, range = 1, target = true },
+ { name = "werecrocodile fire ring", interval = 4100, chance = 25, minDamage = -275, maxDamage = -350, target = false },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 82,
+ mitigation = 2.28,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 25 },
+ { type = COMBAT_ENERGYDAMAGE, percent = -5 },
+ { type = COMBAT_EARTHDAMAGE, percent = 20 },
+ { type = COMBAT_FIREDAMAGE, percent = 35 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = -15 },
+ { type = COMBAT_HOLYDAMAGE, percent = -20 },
+ { type = COMBAT_DEATHDAMAGE, percent = 60 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/lycanthropes/werecrocodile.lua b/data-otservbr-global/monster/lycanthropes/werecrocodile.lua
new file mode 100644
index 00000000000..125c25799c0
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/werecrocodile.lua
@@ -0,0 +1,126 @@
+local mType = Game.createMonsterType("Werecrocodile")
+local monster = {}
+
+monster.description = "a werecrocodile"
+monster.experience = 4140
+monster.outfit = {
+ lookType = 1647,
+ lookHead = 95,
+ lookBody = 117,
+ lookLegs = 4,
+ lookFeet = 116,
+ lookAddons = 0,
+ lookMount = 0,
+}
+
+monster.raceId = 2403
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns",
+}
+
+monster.health = 5280
+monster.maxHealth = 5280
+monster.race = "blood"
+monster.corpse = 43754
+monster.speed = 115
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 100 },
+ { name = "platinum coin", chance = 100000, maxCount = 13 },
+ { name = "werecrocodile tongue", chance = 10800 },
+ { name = "serpent sword", chance = 5910 },
+ { name = "crocodile boots", chance = 8530 },
+ { name = "meat", chance = 5500, maxCount = 4 },
+ { name = "small topaz", chance = 9120, maxCount = 4 },
+ { name = "moonlight crystals", chance = 3000 },
+ { id = 3039, chance = 5120 }, -- red gem
+ { name = "green crystal shard", chance = 2800 },
+ { name = "bonebreaker", chance = 500 },
+ { name = "glorious axe", chance = 2190 },
+ { name = "golden sun coin", chance = 1770 },
+ { id = 43916, chance = 200 }, -- werecrocodile trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -575 },
+ { name = "combat", interval = 2700, chance = 37, type = COMBAT_PHYSICALDAMAGE, minDamage = -175, maxDamage = -325, radius = 1, effect = CONST_ME_BIG_SCRATCH, range = 1, target = true },
+ { name = "combat", interval = 3300, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -375, length = 6, spread = 3, effect = CONST_ME_BLACKSMOKE, target = false },
+ { name = "combat", interval = 3700, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -250, maxDamage = -400, radius = 2, range = 4, effect = CONST_ME_MORTAREA, target = true },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 82,
+ mitigation = 2.28,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 10 },
+ { type = COMBAT_ENERGYDAMAGE, percent = -5 },
+ { type = COMBAT_EARTHDAMAGE, percent = 5 },
+ { type = COMBAT_FIREDAMAGE, percent = 25 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = -25 },
+ { type = COMBAT_HOLYDAMAGE, percent = -15 },
+ { type = COMBAT_DEATHDAMAGE, percent = 25 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/lycanthropes/werepanther.lua b/data-otservbr-global/monster/lycanthropes/werepanther.lua
new file mode 100644
index 00000000000..fb351221cea
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/werepanther.lua
@@ -0,0 +1,131 @@
+local mType = Game.createMonsterType("Werepanther")
+local monster = {}
+
+monster.description = "a werepanther"
+monster.experience = 3550
+monster.outfit = {
+ lookType = 1648,
+ lookHead = 114,
+ lookBody = 114,
+ lookLegs = 67,
+ lookFeet = 122,
+ lookAddons = 1,
+ lookMount = 0,
+}
+
+monster.raceId = 2390
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns, Oskayaat",
+}
+
+monster.health = 4200
+monster.maxHealth = 4200
+monster.race = "blood"
+monster.corpse = 43758
+monster.speed = 125
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {
+ interval = 5000,
+ chance = 10,
+ { text = "Grrr", yell = false },
+}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 80 },
+ { name = "platinum coin", chance = 100000, maxCount = 11 },
+ { name = "werepanther claw", chance = 13820, maxCount = 2 },
+ { name = "golden sickle", chance = 6720 },
+ { name = "meat", chance = 5500, maxCount = 2 },
+ { name = "small ruby", chance = 8470, maxCount = 3 },
+ { name = "moonlight crystals", chance = 2550 },
+ { id = 3039, chance = 1240 }, -- red gem
+ { name = "magma monocle", chance = 3080 },
+ { name = "ripper lance", chance = 850 },
+ { name = "gemmed figurine", chance = 1770 },
+ { id = 817, chance = 2770 }, -- magma amulet
+ { name = "fur armor", chance = 2620 },
+ { id = 43917, chance = 650 }, -- werepanther trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -75, maxDamage = -525 },
+ { name = "combat", interval = 3700, chance = 40, type = COMBAT_FIREDAMAGE, minDamage = -265, maxDamage = -400, radius = 3, effect = CONST_ME_FIREATTACK, target = false },
+ { name = "combat", interval = 2700, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -275, maxDamage = -375, radius = 3, effect = CONST_ME_GROUNDSHAKER, range = 4, target = true },
+ { name = "combat", interval = 2300, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -375, radius = 2, effect = CONST_ME_YELLOWSMOKE, target = false },
+ { name = "combat", interval = 3300, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -175, maxDamage = -300, radius = 2, effect = CONST_ME_MORTAREA, range = 4, target = true },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 72,
+ mitigation = 2.05,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = -10 },
+ { type = COMBAT_EARTHDAMAGE, percent = 10 },
+ { type = COMBAT_FIREDAMAGE, percent = 20 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = -15 },
+ { type = COMBAT_HOLYDAMAGE, percent = -25 },
+ { type = COMBAT_DEATHDAMAGE, percent = 20 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/lycanthropes/weretiger.lua b/data-otservbr-global/monster/lycanthropes/weretiger.lua
new file mode 100644
index 00000000000..6584860abb0
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/weretiger.lua
@@ -0,0 +1,126 @@
+local mType = Game.createMonsterType("Weretiger")
+local monster = {}
+
+monster.description = "a weretiger"
+monster.experience = 3920
+monster.outfit = {
+ lookType = 1646,
+ lookHead = 97,
+ lookBody = 114,
+ lookLegs = 113,
+ lookFeet = 94,
+ lookAddons = 1,
+ lookMount = 0,
+}
+
+monster.raceId = 2386
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns",
+}
+
+monster.health = 5000
+monster.maxHealth = 5000
+monster.race = "blood"
+monster.corpse = 43669
+monster.speed = 125
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 100 },
+ { name = "platinum coin", chance = 100000, maxCount = 13 },
+ { name = "weretiger tooth", chance = 10800 },
+ { name = "furry club", chance = 6230 },
+ { name = "meat", chance = 5500, maxCount = 4 },
+ { name = "violet crystal shard", chance = 3370 },
+ { name = "moonlight crystals", chance = 2550 },
+ { id = 3041, chance = 1200 }, -- blue gem
+ { name = "knight armor", chance = 3000 },
+ { name = "angelic axe", chance = 1430 },
+ { name = "gemmed figurine", chance = 1770 },
+ { id = 817, chance = 1770 }, -- magma amulet
+ { name = "silver moon coin", chance = 510 },
+ { id = 43915, chance = 610 }, -- weretiger trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -50, maxDamage = -625 },
+ { name = "energy chain", interval = 3300, chance = 20, minDamage = -175, maxDamage = -375, range = 3, target = true },
+ { name = "combat", interval = 3300, chance = 20, type = COMBAT_ENERGYDAMAGE, minDamage = -200, maxDamage = -375, length = 5, spread = 2, effect = CONST_ME_BLUE_ENERGY_SPARK, target = false },
+ { name = "combat", interval = 2700, chance = 37, type = COMBAT_PHYSICALDAMAGE, minDamage = -175, maxDamage = -325, radius = 1, effect = CONST_ME_BIG_SCRATCH, range = 1, target = true },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 76,
+ mitigation = 2.16,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = -5 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 25 },
+ { type = COMBAT_EARTHDAMAGE, percent = -15 },
+ { type = COMBAT_FIREDAMAGE, percent = -25 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 30 },
+ { type = COMBAT_HOLYDAMAGE, percent = 10 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/lycanthropes/white_weretiger.lua b/data-otservbr-global/monster/lycanthropes/white_weretiger.lua
new file mode 100644
index 00000000000..f892a60b117
--- /dev/null
+++ b/data-otservbr-global/monster/lycanthropes/white_weretiger.lua
@@ -0,0 +1,124 @@
+local mType = Game.createMonsterType("White Weretiger")
+local monster = {}
+
+monster.description = "a white weretiger"
+monster.experience = 5200
+monster.outfit = {
+ lookType = 1646,
+ lookHead = 19,
+ lookBody = 59,
+ lookLegs = 113,
+ lookFeet = 94,
+ lookAddons = 2,
+ lookMount = 0,
+}
+
+monster.raceId = 2387
+monster.Bestiary = {
+ class = "Lycanthrope",
+ race = BESTY_RACE_LYCANTHROPE,
+ toKill = 2500,
+ FirstUnlock = 100,
+ SecondUnlock = 1000,
+ CharmsPoints = 50,
+ Stars = 4,
+ Occurrence = 0,
+ Locations = "Murky Caverns",
+}
+
+monster.health = 6100
+monster.maxHealth = 6100
+monster.race = "blood"
+monster.corpse = 43762
+monster.speed = 120
+monster.manaCost = 0
+
+monster.changeTarget = {
+ interval = 5000,
+ chance = 20,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ health = 10,
+ damage = 10,
+ random = 10,
+}
+
+monster.flags = {
+ summonable = false,
+ attackable = true,
+ hostile = true,
+ convinceable = false,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = false,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 80,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = true,
+ canWalkOnFire = true,
+ canWalkOnPoison = true,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {}
+
+monster.loot = {
+ { name = "gold coin", chance = 100000, maxCount = 100 },
+ { name = "platinum coin", chance = 100000, maxCount = 20 },
+ { name = "weretiger tooth", chance = 13400 },
+ { name = "beastslayer axe", chance = 3970 },
+ { name = "ham", chance = 5500, maxCount = 2 },
+ { name = "moonlight crystals", chance = 7000 },
+ { name = "white gem", chance = 1650 },
+ { name = "silver moon coin", chance = 2000 },
+ { name = "blue robe", chance = 1160 },
+ { name = "moon pin", chance = 660 },
+ { name = "crystal mace", chance = 500 },
+ { id = 43915, chance = 610 }, -- weretiger trophy
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -585 },
+ { name = "white weretiger ice ring", interval = 3700, chance = 20, minDamage = -300, maxDamage = -425 },
+ { name = "energy ring", interval = 4300, chance = 40, minDamage = -300, maxDamage = -425 },
+ { name = "combat", interval = 2300, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -200, maxDamage = -375, radius = 2, effect = CONST_ME_ICEAREA, target = false },
+}
+
+monster.defenses = {
+ defense = 30,
+ armor = 83,
+ mitigation = 2.25,
+ { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 50, maxDamage = 100, effect = CONST_ME_MAGIC_BLUE, target = false },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = -5 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 25 },
+ { type = COMBAT_EARTHDAMAGE, percent = -20 },
+ { type = COMBAT_FIREDAMAGE, percent = -15 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 40 },
+ { type = COMBAT_HOLYDAMAGE, percent = 25 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = true },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = true },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/magicals/blue_djinn.lua b/data-otservbr-global/monster/magicals/blue_djinn.lua
index 627002e5469..c0a2985933f 100644
--- a/data-otservbr-global/monster/magicals/blue_djinn.lua
+++ b/data-otservbr-global/monster/magicals/blue_djinn.lua
@@ -34,7 +34,7 @@ monster.speed = 110
monster.manaCost = 0
monster.faction = FACTION_MARID
-monster.enemyFactions = { FACTION_EFREET, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_EFREET }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/choking_fear.lua b/data-otservbr-global/monster/magicals/choking_fear.lua
index 16361bab0e9..9e95ff23aa9 100644
--- a/data-otservbr-global/monster/magicals/choking_fear.lua
+++ b/data-otservbr-global/monster/magicals/choking_fear.lua
@@ -109,7 +109,7 @@ monster.attacks = {
-- poison
{ name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -700, maxDamage = -900, length = 5, spread = 0, effect = CONST_ME_HITBYPOISON, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -300, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 1, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_SLEEP, target = true, duration = 15000 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -130, maxDamage = -300, radius = 4, effect = CONST_ME_SOUND_RED, target = false },
{ name = "choking fear drown", interval = 2000, chance = 20, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -250, maxDamage = -500, radius = 4, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true },
diff --git a/data-otservbr-global/monster/magicals/crypt_warden.lua b/data-otservbr-global/monster/magicals/crypt_warden.lua
index e5a2db7ec77..7eedb1ff920 100644
--- a/data-otservbr-global/monster/magicals/crypt_warden.lua
+++ b/data-otservbr-global/monster/magicals/crypt_warden.lua
@@ -34,7 +34,7 @@ monster.speed = 145
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/efreet.lua b/data-otservbr-global/monster/magicals/efreet.lua
index b0955162290..d20dd609a9f 100644
--- a/data-otservbr-global/monster/magicals/efreet.lua
+++ b/data-otservbr-global/monster/magicals/efreet.lua
@@ -34,7 +34,7 @@ monster.speed = 117
monster.manaCost = 0
monster.faction = FACTION_EFREET
-monster.enemyFactions = { FACTION_MARID, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_MARID }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/feral_sphinx.lua b/data-otservbr-global/monster/magicals/feral_sphinx.lua
index 481057d5654..376daa84443 100644
--- a/data-otservbr-global/monster/magicals/feral_sphinx.lua
+++ b/data-otservbr-global/monster/magicals/feral_sphinx.lua
@@ -75,21 +75,28 @@ monster.voices = {
monster.loot = {
{ name = "platinum coin", chance = 100000, maxCount = 3 },
- { name = "wand of draconia", chance = 4770 },
- { name = "sphinx feather", chance = 3450 },
- { name = "fire axe", chance = 2650 },
- { id = 31438, chance = 3450 }, -- sphinx tiara
- { name = "magma legs", chance = 1860 },
- { name = "magma monocle", chance = 1590 },
- { name = "magma boots", chance = 2120 },
- { name = "magma amulet", chance = 7160 },
- { name = "wand of inferno", chance = 7690 },
- { name = "dragon necklace", chance = 800 },
+ { name = "green crystal shard", chance = 8740 },
+ { name = "cyan crystal fragment", chance = 8620 },
+ { id = 3039, chance = 8390 }, -- red gem
+ { name = "magma amulet", chance = 6060 },
+ { name = "wand of inferno", chance = 5710 },
+ { name = "small sapphire", chance = 5590, maxCount = 2 },
+ { name = "dragon necklace", chance = 5590 },
+ { name = "blue gem", chance = 5480 },
+ { name = "sphinx feather", chance = 5480 },
+ { name = "sphinx tiara", chance = 5240 },
+ { name = "fire axe", chance = 4200 },
+ { name = "wand of draconia", chance = 2910 },
+ { name = "green gem", chance = 2680 },
+ { name = "magma monocle", chance = 1400 },
+ { name = "magma boots", chance = 1280 },
+ { name = "small enchanted emerald", chance = 1050, maxCount = 2 },
+ { name = "magma legs", chance = 930 },
}
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 },
- { name = "fire wave", interval = 2000, chance = 15, minDamage = -350, maxDamage = -500, length = 1, spread = 1, effect = CONST_ME_FIREAREA, target = true },
+ { name = "fire wave", interval = 2000, chance = 15, minDamage = -350, maxDamage = -500, length = 1, spread = 0, effect = CONST_ME_FIREAREA, target = true },
{ name = "combat", interval = 2000, chance = 25, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -500, radius = 4, effect = CONST_ME_ENERGYAREA, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_FIREDAMAGE, minDamage = -350, maxDamage = -550, range = 1, shootEffect = CONST_ANI_FIRE, target = false },
{ name = "combat", interval = 2000, chance = 18, type = COMBAT_HOLYDAMAGE, minDamage = -400, maxDamage = -580, length = 6, spread = 3, effect = CONST_ME_HOLYAREA, target = false },
diff --git a/data-otservbr-global/monster/magicals/green_djinn.lua b/data-otservbr-global/monster/magicals/green_djinn.lua
index 3f7f6092068..31b13556cff 100644
--- a/data-otservbr-global/monster/magicals/green_djinn.lua
+++ b/data-otservbr-global/monster/magicals/green_djinn.lua
@@ -35,7 +35,7 @@ monster.speed = 110
monster.manaCost = 0
monster.faction = FACTION_EFREET
-monster.enemyFactions = { FACTION_MARID, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_MARID }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/guzzlemaw.lua b/data-otservbr-global/monster/magicals/guzzlemaw.lua
index d402a6921da..79a58ca0c50 100644
--- a/data-otservbr-global/monster/magicals/guzzlemaw.lua
+++ b/data-otservbr-global/monster/magicals/guzzlemaw.lua
@@ -23,8 +23,7 @@ monster.Bestiary = {
CharmsPoints = 50,
Stars = 4,
Occurrence = 0,
- Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul \z
- (south of the Depot and west of the entrance to Roshamuul Prison).",
+ Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul (south of the Depot and west of the entrance to Roshamuul Prison).",
}
monster.health = 6400
@@ -113,7 +112,7 @@ monster.attacks = {
{ name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, effect = CONST_ME_DRAWBLOOD, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -900, length = 8, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false },
}
diff --git a/data-otservbr-global/monster/magicals/lamassu.lua b/data-otservbr-global/monster/magicals/lamassu.lua
index b59aa14c364..2d22d43c092 100644
--- a/data-otservbr-global/monster/magicals/lamassu.lua
+++ b/data-otservbr-global/monster/magicals/lamassu.lua
@@ -34,7 +34,7 @@ monster.speed = 160
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
@@ -94,9 +94,10 @@ monster.loot = {
}
monster.attacks = {
- { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -600 },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -200, maxDamage = -485, radius = 3, effect = CONST_ME_HOLYAREA, target = false },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -405, range = 5, radius = 3, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true },
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500 },
+ { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -400, maxDamage = -500, radius = 1, effect = CONST_ME_HOLYAREA, target = false },
+ { name = "combat", interval = 2000, chance = 20, type = COMBAT_HOLYDAMAGE, minDamage = -300, maxDamage = -500, radius = 3, effect = CONST_ME_HOLYAREA, target = false },
+ { name = "combat", interval = 2000, chance = 20, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -405, range = 5, radius = 2, shootEffect = CONST_ANI_SMALLEARTH, effect = CONST_ME_SMALLPLANTS, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/magicals/lumbering_carnivor.lua b/data-otservbr-global/monster/magicals/lumbering_carnivor.lua
index b6878c11e06..7eee6229eaa 100644
--- a/data-otservbr-global/monster/magicals/lumbering_carnivor.lua
+++ b/data-otservbr-global/monster/magicals/lumbering_carnivor.lua
@@ -76,21 +76,21 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 5 },
- { name = "blue glass plate", chance = 100000, maxCount = 3 },
- { id = 3264, chance = 15000 }, -- sword
- { name = "axe", chance = 14000 },
- { name = "ice rapier", chance = 12000 },
- { name = "glorious axe", chance = 6100 },
- { name = "blue robe", chance = 4600 },
- { name = "two handed sword", chance = 13000 },
- { name = "fur armor", chance = 5400 },
- { id = 281, chance = 3200 }, -- giant shimmering pearl (green)
- { name = "green crystal shard", chance = 3100 },
- { name = "violet gem", chance = 4000 },
- { name = "green gem", chance = 4800 },
- { name = "blue gem", chance = 4000 },
- { name = "focus cape", chance = 3000 },
+ { name = "platinum coin", chance = 64770, maxCount = 3 },
+ { name = "blue glass plate", chance = 20840, maxCount = 3 },
+ { name = "axe", chance = 14620 },
+ { name = "ice rapier", chance = 7600 },
+ { id = 3264, chance = 5500 }, -- sword
+ { id = 281, chance = 1830 }, -- giant shimmering pearl (green)
+ { name = "green gem", chance = 1680 },
+ { name = "violet gem", chance = 1560 },
+ { name = "glorious axe", chance = 1530 },
+ { name = "two handed sword", chance = 1490 },
+ { name = "blue robe", chance = 760 },
+ { name = "blue gem", chance = 990 },
+ { name = "fur armor", chance = 950 },
+ { name = "green crystal shard", chance = 920 },
+ { name = "focus cape", chance = 80 },
}
monster.attacks = {
diff --git a/data-otservbr-global/monster/magicals/marid.lua b/data-otservbr-global/monster/magicals/marid.lua
index fa14fce413e..36340b8ccb9 100644
--- a/data-otservbr-global/monster/magicals/marid.lua
+++ b/data-otservbr-global/monster/magicals/marid.lua
@@ -34,7 +34,7 @@ monster.speed = 117
monster.manaCost = 0
monster.faction = FACTION_MARID
-monster.enemyFactions = { FACTION_EFREET, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_EFREET }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/menacing_carnivor.lua b/data-otservbr-global/monster/magicals/menacing_carnivor.lua
index fa314084b33..fed50464588 100644
--- a/data-otservbr-global/monster/magicals/menacing_carnivor.lua
+++ b/data-otservbr-global/monster/magicals/menacing_carnivor.lua
@@ -76,28 +76,28 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 6 },
- { name = "morning star", chance = 100000 },
- { name = "terra rod", chance = 15550 },
- { name = "small ruby", chance = 15000 },
- { name = "crystal sword", chance = 25000 },
- { name = "ultimate mana potion", chance = 50000 },
- { name = "wand of dragonbreath", chance = 15000 },
- { name = "machete", chance = 30000 },
- { name = "iron helmet", chance = 20000 },
- { name = "serpent sword", chance = 18000 },
- { name = "heavy machete", chance = 17000 },
- { name = "terra legs", chance = 6000 },
- { name = "knight legs", chance = 4500 },
- { name = "wand of starstorm", chance = 8000 },
- { name = "wand of voodoo", chance = 7100 },
- { name = "violet glass plate", chance = 6200 },
- { name = "small enchanted ruby", chance = 1400 },
- { name = "green crystal fragment", chance = 1600 },
- { name = "onyx chip", chance = 9800 },
- { name = "opal", chance = 2000 },
- { name = "tiger eye", chance = 3000 },
- { name = "wand of decay", chance = 8700 },
+ { name = "platinum coin", chance = 65410, maxCount = 8 },
+ { name = "morning star", chance = 16730 },
+ { name = "ultimate mana potion", chance = 9820 },
+ { name = "violet glass plate", chance = 691 },
+ { name = "crystal sword", chance = 4750 },
+ { name = "terra rod", chance = 4480 },
+ { name = "small ruby", chance = 4000 },
+ { name = "onyx chip", chance = 3350 },
+ { name = "green crystal fragment", chance = 3180 },
+ { name = "small enchanted ruby", chance = 2050 },
+ { name = "terra legs", chance = 2000 },
+ { name = "knight legs", chance = 1780 },
+ { name = "machete", chance = 1730 },
+ { name = "wand of voodoo", chance = 1570 },
+ { name = "heavy machete", chance = 1240 },
+ { name = "wand of starstorm", chance = 1240 },
+ { name = "wand of dragonbreath", chance = 970 },
+ { name = "tiger eye", chance = 920 },
+ { name = "opal", chance = 810 },
+ { name = "iron helmet", chance = 760 },
+ { name = "serpent sword", chance = 700 },
+ { name = "wand of decay", chance = 490 },
}
monster.attacks = {
diff --git a/data-otservbr-global/monster/magicals/phantasm_summon.lua b/data-otservbr-global/monster/magicals/phantasm_summon.lua
index 152680243a9..4926c01aada 100644
--- a/data-otservbr-global/monster/magicals/phantasm_summon.lua
+++ b/data-otservbr-global/monster/magicals/phantasm_summon.lua
@@ -3,7 +3,7 @@ local monster = {}
monster.name = "Phantasm"
monster.description = "a phantasm"
-monster.experience = 4400
+monster.experience = 1
monster.outfit = {
lookType = 241,
lookHead = 0,
@@ -14,8 +14,8 @@ monster.outfit = {
lookMount = 0,
}
-monster.health = 3950
-monster.maxHealth = 3950
+monster.health = 65
+monster.maxHealth = 65
monster.race = "undead"
monster.corpse = 6343
monster.speed = 170
diff --git a/data-otservbr-global/monster/magicals/shock_head.lua b/data-otservbr-global/monster/magicals/shock_head.lua
index 22c1e30890e..0394008118d 100644
--- a/data-otservbr-global/monster/magicals/shock_head.lua
+++ b/data-otservbr-global/monster/magicals/shock_head.lua
@@ -86,7 +86,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -798 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -300, length = 5, spread = 2, effect = CONST_ME_BLACKSMOKE, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false, duration = 7500 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, length = 8, spread = 0, effect = CONST_ME_PURPLEENERGY, target = false, duration = 7500 },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -350, radius = 4, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_STONES, target = true },
{ name = "shock head skill reducer 1", interval = 2000, chance = 5, range = 5, target = false },
{ name = "shock head skill reducer 2", interval = 2000, chance = 5, target = false },
diff --git a/data-otservbr-global/monster/magicals/sphinx.lua b/data-otservbr-global/monster/magicals/sphinx.lua
index 2ca6c8f4dba..b7b305db787 100644
--- a/data-otservbr-global/monster/magicals/sphinx.lua
+++ b/data-otservbr-global/monster/magicals/sphinx.lua
@@ -34,7 +34,7 @@ monster.speed = 145
monster.manaCost = 0
monster.faction = FACTION_ANUMA
-monster.enemyFactions = { FACTION_FAFNAR, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_FAFNAR }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/magicals/spiky_carnivor.lua b/data-otservbr-global/monster/magicals/spiky_carnivor.lua
index e39750e556a..f8a29ef234f 100644
--- a/data-otservbr-global/monster/magicals/spiky_carnivor.lua
+++ b/data-otservbr-global/monster/magicals/spiky_carnivor.lua
@@ -76,24 +76,24 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 6 },
- { name = "green glass plate", chance = 12000, maxCount = 17 },
- { name = "blue crystal splinter", chance = 11500 },
- { name = "brown crystal splinter", chance = 11000 },
- { name = "dark armor", chance = 10000 },
- { name = "guardian shield", chance = 9000 },
- { name = "rainbow quartz", chance = 8500 },
- { name = "blue robe", chance = 8000 },
- { name = "glacier amulet", chance = 7500 },
- { name = "lightning pendant", chance = 2200 },
- { name = "prismatic quartz", chance = 6500 },
- { name = "talon", chance = 6000 },
- { name = "terra amulet", chance = 5500 },
- { name = "warrior helmet", chance = 4000 },
- { name = "shockwave amulet", chance = 2550 },
- { name = "terra mantle", chance = 4050 },
- { name = "buckle", chance = 250 },
- { name = "doublet", chance = 250 },
+ { name = "platinum coin", chance = 66230, maxCount = 6 },
+ { name = "dark armor", chance = 13870 },
+ { name = "green glass plate", chance = 10490, maxCount = 2 },
+ { name = "blue crystal splinter", chance = 7590 },
+ { name = "brown crystal splinter", chance = 7330 },
+ { name = "guardian shield", chance = 5010 },
+ { name = "warrior helmet", chance = 2980 },
+ { name = "rainbow quartz", chance = 2540, maxCount = 2 },
+ { name = "talon", chance = 2000 },
+ { name = "glacier amulet", chance = 1920 },
+ { name = "terra amulet", chance = 1920 },
+ { name = "blue robe", chance = 1670 },
+ { name = "prismatic quartz", chance = 1380 },
+ { name = "lightning pendant", chance = 1270 },
+ { name = "doublet", chance = 360 },
+ { name = "terra mantle", chance = 330 },
+ { name = "buckle", chance = 180 },
+ { name = "shockwave amulet", chance = 150 },
}
monster.attacks = {
diff --git a/data-otservbr-global/monster/mammals/white_lion.lua b/data-otservbr-global/monster/mammals/white_lion.lua
index 9f47df6e6b9..0330aca13c0 100644
--- a/data-otservbr-global/monster/mammals/white_lion.lua
+++ b/data-otservbr-global/monster/mammals/white_lion.lua
@@ -16,7 +16,7 @@ monster.outfit = {
monster.raceId = 1967
monster.Bestiary = {
class = "Mammal",
-
+ race = BESTY_RACE_MAMMAL,
toKill = 2500,
FirstUnlock = 100,
SecondUnlock = 1000,
diff --git a/data-otservbr-global/monster/mammals/white_tiger.lua b/data-otservbr-global/monster/mammals/white_tiger.lua
new file mode 100644
index 00000000000..3d34267d2fb
--- /dev/null
+++ b/data-otservbr-global/monster/mammals/white_tiger.lua
@@ -0,0 +1,112 @@
+local mType = Game.createMonsterType("White Tiger")
+local monster = {}
+
+monster.description = "a white tiger"
+monster.experience = 40
+monster.outfit = {
+ lookType = 1649,
+ lookHead = 0,
+ lookBody = 0,
+ lookLegs = 0,
+ lookFeet = 0,
+ lookAddons = 0,
+ lookMount = 0,
+}
+
+monster.raceId = 2391
+monster.Bestiary = {
+ class = "Mammal",
+ race = BESTY_RACE_MAMMAL,
+ toKill = 500,
+ FirstUnlock = 25,
+ SecondUnlock = 250,
+ CharmsPoints = 15,
+ Stars = 2,
+ Occurrence = 0,
+ Locations = "Oskayaat",
+}
+
+monster.health = 75
+monster.maxHealth = 75
+monster.race = "blood"
+monster.corpse = 43771
+monster.speed = 110
+monster.manaCost = 420
+
+monster.changeTarget = {
+ interval = 4000,
+ chance = 0,
+}
+
+monster.strategiesTarget = {
+ nearest = 70,
+ damage = 30,
+}
+
+monster.flags = {
+ summonable = true,
+ attackable = true,
+ hostile = true,
+ convinceable = true,
+ pushable = false,
+ rewardBoss = false,
+ illusionable = true,
+ canPushItems = true,
+ canPushCreatures = true,
+ staticAttackChance = 70,
+ targetDistance = 1,
+ runHealth = 0,
+ healthHidden = false,
+ isBlockable = false,
+ canWalkOnEnergy = false,
+ canWalkOnFire = false,
+ canWalkOnPoison = false,
+}
+
+monster.light = {
+ level = 0,
+ color = 0,
+}
+
+monster.voices = {
+ interval = 5000,
+ chance = 10,
+}
+
+monster.loot = {
+ { name = "meat", chance = 35190, maxCount = 4 },
+ { name = "striped fur", chance = 10830 },
+}
+
+monster.attacks = {
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -40 },
+}
+
+monster.defenses = {
+ defense = 15,
+ armor = 5,
+ mitigation = 0.38,
+ { name = "speed", interval = 2000, chance = 15, speedChange = 200, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
+}
+
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = -10 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = -10 },
+}
+
+monster.immunities = {
+ { type = "paralyze", condition = false },
+ { type = "outfit", condition = false },
+ { type = "invisible", condition = false },
+ { type = "bleed", condition = false },
+}
+
+mType:register(monster)
diff --git a/data-otservbr-global/monster/plants/carniphila.lua b/data-otservbr-global/monster/plants/carniphila.lua
index 4251cde140a..ab52039d6a9 100644
--- a/data-otservbr-global/monster/plants/carniphila.lua
+++ b/data-otservbr-global/monster/plants/carniphila.lua
@@ -88,7 +88,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -100, condition = { type = CONDITION_POISON, totalDamage = 100, interval = 4000 } },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -60, maxDamage = -95, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -40, maxDamage = -130, radius = 3, effect = CONST_ME_POISONAREA, target = false },
}
diff --git a/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua b/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua
index d6ad2f68864..6f27b9f17f2 100644
--- a/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua
+++ b/data-otservbr-global/monster/quests/ancient_tombs/thalas.lua
@@ -92,7 +92,7 @@ monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -900 },
{ name = "combat", interval = 2000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -150, maxDamage = -650, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false },
{ name = "melee", interval = 3000, chance = 20, minDamage = -150, maxDamage = -650 },
- { name = "speed", interval = 1000, chance = 6, speedChange = -800, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
+ { name = "speed", interval = 1000, chance = 6, speedChange = -100, range = 7, effect = CONST_ME_MAGIC_RED, target = false, duration = 20000 },
-- poison
{ name = "condition", type = CONDITION_POISON, interval = 1000, chance = 15, minDamage = -34, maxDamage = -35, radius = 5, effect = CONST_ME_POISONAREA, target = false },
{ name = "combat", interval = 3000, chance = 17, type = COMBAT_EARTHDAMAGE, minDamage = -55, maxDamage = -550, length = 8, spread = 3, effect = CONST_ME_POISONAREA, target = false },
diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua b/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua
index 009f8a83209..6d723c594ea 100644
--- a/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua
+++ b/data-otservbr-global/monster/quests/cults_of_tibia/animated_guzzlemaw.lua
@@ -99,7 +99,7 @@ monster.attacks = {
{ name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, effect = CONST_ME_DRAWBLOOD, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -900, length = 8, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = true },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false },
}
diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua b/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua
index f91e51a5793..56b2ed30216 100644
--- a/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua
+++ b/data-otservbr-global/monster/quests/cults_of_tibia/animated_ogre_brute.lua
@@ -80,7 +80,6 @@ monster.loot = {
{ id = 22172, chance = 600 }, -- ogre choppa
{ id = 22171, chance = 800 }, -- ogre klubba
{ id = 3465, chance = 500 }, -- pot
- { id = 8906, chance = 200 }, -- heavily rusted helmet
{ id = 22192, chance = 300 }, -- shamanic mask
}
diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua
index 26aceb32010..3c9b9b0bba4 100644
--- a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua
+++ b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/essence_of_malice.lua
@@ -101,7 +101,7 @@ monster.attacks = {
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -80, maxDamage = -230, range = 7, effect = CONST_ME_MAGIC_RED, target = true },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -120, maxDamage = -250, length = 8, spread = 0, effect = CONST_ME_LOSEENERGY, target = false },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -110, maxDamage = -180, radius = 4, effect = CONST_ME_MORTAREA, target = false },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, range = 7, effect = CONST_ME_SMALLCLOUDS, target = true, duration = 30000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua
index ee9b5d37a66..e3eef7671c6 100644
--- a/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua
+++ b/data-otservbr-global/monster/quests/cults_of_tibia/bosses/ravenous_hunger.lua
@@ -14,7 +14,6 @@ monster.outfit = {
}
monster.events = {
- "LeidenHeal",
"CultsOfTibiaBossDeath",
}
diff --git a/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua b/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua
index 973fa02afea..e3c0cd883c5 100644
--- a/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua
+++ b/data-otservbr-global/monster/quests/ferumbras_ascendant/ferumbras_essence.lua
@@ -38,10 +38,10 @@ monster.strategiesTarget = {
monster.flags = {
summonable = false,
- attackable = true,
+ attackable = false,
hostile = true,
convinceable = false,
- pushable = false,
+ pushable = true,
rewardBoss = false,
illusionable = false,
canPushItems = true,
diff --git a/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua b/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua
index 8d4bebd6665..abfc779efc1 100644
--- a/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua
+++ b/data-otservbr-global/monster/quests/forgotten_knowledge/thorn_minion.lua
@@ -67,7 +67,7 @@ monster.loot = {}
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = -0, maxDamage = -264 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -100, maxDamage = -195, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = false, duration = 30000 },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -280, radius = 4, effect = CONST_ME_GROUNDSHAKER, target = false },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -200, maxDamage = -400, length = 4, spread = 0, effect = CONST_ME_CARNIPHILA, target = false },
}
diff --git a/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua b/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua
index 8637363a949..921c63ad286 100644
--- a/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua
+++ b/data-otservbr-global/monster/quests/grave_danger/bosses/sir_nictros.lua
@@ -40,7 +40,7 @@ monster.flags = {
hostile = true,
convinceable = false,
pushable = false,
- rewardBoss = true,
+ rewardBoss = false,
illusionable = false,
canPushItems = true,
canPushCreatures = true,
@@ -122,18 +122,4 @@ monster.immunities = {
{ type = "bleed", condition = false },
}
-mType.onThink = function(monster, interval) end
-
-mType.onAppear = function(monster, creature)
- if monster:getType():isRewardBoss() then
- monster:setReward(true)
- end
-end
-
-mType.onDisappear = function(monster, creature) end
-
-mType.onMove = function(monster, creature, fromPosition, toPosition) end
-
-mType.onSay = function(monster, creature, type, message) end
-
mType:register(monster)
diff --git a/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua b/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua
index 7c8c548d9e0..ba37eb8168e 100644
--- a/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua
+++ b/data-otservbr-global/monster/quests/grave_danger/frozen_soul.lua
@@ -62,8 +62,8 @@ monster.voices = {
monster.loot = {}
monster.attacks = {
- { name = "combat", interval = 2000, chance = 40, type = COMBAT_ICEDAMAGE, minDamage = -325, maxDamage = -450, range = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true },
- { name = "combat", interval = 3500, chance = 30, type = COMBAT_LIFEDRAIN, minDamage = -275, maxDamage = -400, range = 5, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_MAGIC_RED, target = true },
+ { name = "combat", interval = 2000, chance = 35, type = COMBAT_ICEDAMAGE, minDamage = -225, maxDamage = -400, range = 5, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true },
+ { name = "combat", interval = 3500, chance = 25, type = COMBAT_LIFEDRAIN, minDamage = -175, maxDamage = -350, range = 5, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_MAGIC_RED, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua b/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua
index e2cff3e976d..34295fba4f9 100644
--- a/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua
+++ b/data-otservbr-global/monster/quests/grave_danger/soul_scourge.lua
@@ -62,8 +62,8 @@ monster.voices = {
monster.loot = {}
monster.attacks = {
- { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -700 },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -500, maxDamage = -750, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
+ { name = "melee", interval = 2000, chance = 80, minDamage = 0, maxDamage = -500 },
+ { name = "combat", interval = 3500, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -500, range = 5, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua
index f49fa38af71..939c159e83e 100644
--- a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua
+++ b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_lord.lua
@@ -64,9 +64,6 @@ monster.voices = {
monster.loot = {}
---monster.attacks = {
---}
-
monster.defenses = {
defense = 5,
armor = 10,
diff --git a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua
index 3fd5039cc06..e0fc91bedb3 100644
--- a/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua
+++ b/data-otservbr-global/monster/quests/in_service_of_yalahar/rift_phantom.lua
@@ -61,9 +61,6 @@ monster.voices = {
monster.loot = {}
---monster.attacks = {
---}
-
monster.defenses = {
defense = 5,
armor = 10,
diff --git a/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua b/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua
index b530d1e96c1..fd98f95e5b0 100644
--- a/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua
+++ b/data-otservbr-global/monster/quests/pits_of_inferno/the_plasmother.lua
@@ -89,7 +89,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, skill = 30, attack = 50 },
- { name = "speed", interval = 1000, chance = 8, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 },
+ { name = "speed", interval = 1000, chance = 8, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 10000 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -350, radius = 4, effect = CONST_ME_POISONAREA, target = false },
{ name = "combat", interval = 3000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -200, maxDamage = -530, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_HITBYPOISON, target = true },
}
diff --git a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
index f233c41680b..b0bc59364d6 100644
--- a/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
+++ b/data-otservbr-global/monster/quests/primal_ordeal_quest/the_primal_menace.lua
@@ -111,17 +111,15 @@ monster.voices = {
chance = 10,
}
-monster.loot = {
- { name = "primal bag", chance = 50 },
-}
+monster.loot = {}
monster.attacks = {
- { name = "melee", interval = 2000, chance = 85, minDamage = -0, maxDamage = -763 },
- { name = "combat", interval = 4000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -1500, maxDamage = -2200, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false },
- { name = "combat", interval = 2500, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -700, maxDamage = -1000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false },
- { name = "big death wave", interval = 3500, chance = 20, minDamage = -250, maxDamage = -300, target = false },
- { name = "combat", interval = 5000, chance = 15, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -1200, maxDamage = -1300, range = 4, target = false },
- { name = "combat", interval = 2700, chance = 30, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -600, maxDamage = -1800, range = 4, target = true },
+ { name = "melee", interval = 2000, chance = 100, minDamage = -0, maxDamage = -763 },
+ { name = "combat", interval = 4000, chance = 25, type = COMBAT_EARTHDAMAGE, minDamage = -1500, maxDamage = -2200, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false },
+ { name = "combat", interval = 2500, chance = 35, type = COMBAT_FIREDAMAGE, minDamage = -700, maxDamage = -1000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false },
+ { name = "big death wave", interval = 3500, chance = 25, minDamage = -250, maxDamage = -300, target = false },
+ { name = "combat", interval = 5000, chance = 25, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -1200, maxDamage = -1300, range = 4, target = false },
+ { name = "combat", interval = 2700, chance = 35, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -600, maxDamage = -1800, range = 4, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua b/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua
index 717a1fe0814..5fbd324c0fd 100644
--- a/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua
+++ b/data-otservbr-global/monster/quests/the_dream_courts/lucifuga_aranea.lua
@@ -73,7 +73,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -250, condition = { type = CONDITION_POISON, totalDamage = 160, interval = 4000 } },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -100, range = 7, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true },
{ name = "speed", interval = 2000, chance = 20, speedChange = -600, range = 7, shootEffect = CONST_ANI_SNOWBALL, target = true, duration = 10000 },
}
diff --git a/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua b/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua
index 9a65a1c8ef2..44bf1a162e1 100644
--- a/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua
+++ b/data-otservbr-global/monster/quests/the_elemental_spheres/ice_overlord.lua
@@ -74,7 +74,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -400 },
- { name = "speed", interval = 2000, chance = 18, speedChange = -800, radius = 6, effect = CONST_ME_ICETORNADO, target = false, duration = 5000 },
+ { name = "speed", interval = 2000, chance = 18, speedChange = -100, radius = 6, effect = CONST_ME_ICETORNADO, target = false, duration = 5000 },
{ name = "combat", interval = 1000, chance = 9, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -400, range = 7, shootEffect = CONST_ANI_SMALLICE, effect = CONST_ME_ICEATTACK, target = true },
}
diff --git a/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua
index 87ef3250d85..3893adff8c4 100644
--- a/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua
+++ b/data-otservbr-global/monster/quests/the_explorer_society/blue_butterfly.lua
@@ -14,6 +14,21 @@ monster.outfit = {
lookMount = 0,
}
+monster.raceId = 213
+monster.Bestiary = {
+ class = "Vermin",
+ race = BESTY_RACE_VERMIN,
+ toKill = 25,
+ FirstUnlock = 5,
+ SecondUnlock = 10,
+ CharmsPoints = 1,
+ Stars = 0,
+ Occurrence = 0,
+ Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z
+ Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z
+ Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.",
+}
+
monster.health = 2
monster.maxHealth = 2
monster.race = "venom"
diff --git a/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua
index 7003fdd3878..1a8f0d8c2a0 100644
--- a/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua
+++ b/data-otservbr-global/monster/quests/the_explorer_society/pink_butterfly.lua
@@ -14,6 +14,21 @@ monster.outfit = {
lookMount = 0,
}
+monster.raceId = 213
+monster.Bestiary = {
+ class = "Vermin",
+ race = BESTY_RACE_VERMIN,
+ toKill = 25,
+ FirstUnlock = 5,
+ SecondUnlock = 10,
+ CharmsPoints = 1,
+ Stars = 0,
+ Occurrence = 0,
+ Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z
+ Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z
+ Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.",
+}
+
monster.health = 2
monster.maxHealth = 2
monster.race = "venom"
diff --git a/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua b/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua
index d2c42510683..4b71858189d 100644
--- a/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua
+++ b/data-otservbr-global/monster/quests/the_explorer_society/purple_butterfly.lua
@@ -14,6 +14,21 @@ monster.outfit = {
lookMount = 0,
}
+monster.raceId = 213
+monster.Bestiary = {
+ class = "Vermin",
+ race = BESTY_RACE_VERMIN,
+ toKill = 25,
+ FirstUnlock = 5,
+ SecondUnlock = 10,
+ CharmsPoints = 1,
+ Stars = 0,
+ Occurrence = 0,
+ Locations = "Ab'Dendriel, Ab'Dendriel Surroundings, Carlin, Cormaya, Edron Surroundings, \z
+ Feyrist Meadows, Fibula, Fields of Glory, Green Claw Swamp, Issavi, Kazordoon Surroundings, Meriana, \z
+ Outlaw Camp, Port Hope Surroundings, Stonehome, Thais Surroundings, Venore Southern Swamp, Venore Surroundings.",
+}
+
monster.health = 2
monster.maxHealth = 2
monster.race = "venom"
diff --git a/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua b/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua
index 820931f81c0..aba0cc866b4 100644
--- a/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua
+++ b/data-otservbr-global/monster/quests/the_order_of_lion/bosses/ancient_lion_knight.lua
@@ -21,7 +21,7 @@ monster.speed = 130
monster.manaCost = 0
monster.faction = FACTION_LIONUSURPERS
-monster.enemyFactions = { FACTION_LION, FACTION_PLAYER }
+monster.enemyFactions = { FACTION_PLAYER, FACTION_LION }
monster.changeTarget = {
interval = 4000,
diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua
index bd3a786b365..09d4abb0935 100644
--- a/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua
+++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/ghulosh.lua
@@ -96,8 +96,8 @@ monster.loot = {
{ name = "dreaded cleaver", chance = 1000 },
{ name = "mercenary sword", chance = 1000 },
{ id = 28341, chance = 1000 }, -- tessellated wall
- { id = 8900, chance = 1000 }, -- heavily rusted shield
- { id = 8906, chance = 1000 }, -- heavily rusted helmet
+ { name = "slightly rusted shield", chance = 5880 },
+ { name = "slightly rusted helmet", chance = 35290 },
{ name = "epaulette", chance = 500 },
{ name = "giant emerald", chance = 500 },
{ name = "unliving demonbone", chance = 500 },
diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua
index b00bffd6d60..e5a1e3d6424 100644
--- a/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua
+++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/gorzindel.lua
@@ -98,8 +98,7 @@ monster.loot = {
{ name = "magic sulphur", chance = 1000, maxCount = 2 },
{ name = "muck rod", chance = 1000 },
{ id = 3039, chance = 1000 }, -- red gem
- { id = 8906, chance = 1000 }, -- heavily rusted helmet
- { id = 8900, chance = 1000 }, -- heavily rusted shield
+ { name = "slightly rusted shield", chance = 11760 },
{ name = "silver Token", chance = 1000, maxCount = 6 },
{ name = "sinister book", chance = 1000 },
{ name = "spellbook of warding", chance = 1000 },
diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua
index af9c6f0218a..86714879320 100644
--- a/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua
+++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/grand_master_oberon_functions.lua
@@ -24,7 +24,7 @@ GrandMasterOberonResponses = {
[6] = { msg = "Excuse me but I still do not get the message!", msg2 = oberonOthersMessages[2] },
[7] = { msg = "Dare strike up a Minnesang and you will receive your last accolade!", msg2 = oberonOthersMessages[1] },
[8] = { msg = "Then why are we fighting alone right now?", msg2 = oberonOthersMessages[2] },
- [9] = { msg = "SEHWO ASIMO, TOLIDO ESD!", msg2 = oberonOthersMessages[2] },
+ [9] = { msg = "SEHWO ASIMO, TOLIDO ESD", msg2 = oberonOthersMessages[2] },
}
GrandMasterOberonConfig = {
diff --git a/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua b/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua
index 491b946adba..bd4f897e03a 100644
--- a/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua
+++ b/data-otservbr-global/monster/quests/the_secret_library/bosses/the_scourge_of_oblivion.lua
@@ -134,7 +134,7 @@ monster.attacks = {
{ name = "choking fear drown", interval = 2000, chance = 20, target = false },
{ name = "combat", interval = 2000, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -450, maxDamage = -1400, radius = 4, shootEffect = CONST_ANI_SUDDENDEATH, effect = CONST_ME_MORTAREA, target = true },
{ name = "combat", interval = 1000, chance = 10, type = COMBAT_MANADRAIN, minDamage = -800, maxDamage = -2300, radius = 8, effect = CONST_ME_MAGIC_GREEN, target = false },
- { name = "speed", interval = 1000, chance = 12, speedChange = -800, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 60000 },
+ { name = "speed", interval = 1000, chance = 12, speedChange = -100, radius = 6, effect = CONST_ME_POISONAREA, target = false, duration = 60000 },
{ name = "strength", interval = 1000, chance = 8, radius = 5, effect = CONST_ME_HITAREA, target = false },
{ name = "combat", interval = 1000, chance = 34, type = COMBAT_FIREDAMAGE, minDamage = -100, maxDamage = -700, range = 7, radius = 7, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_FIREAREA, target = true },
{ name = "combat", interval = 1000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -300, maxDamage = -950, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false },
diff --git a/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua b/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua
index be0e44216a9..621717cd277 100644
--- a/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua
+++ b/data-otservbr-global/monster/quests/the_secret_library/lokathmor.lua
@@ -98,7 +98,7 @@ monster.loot = {
{ name = "silver token", chance = 30000, maxCount = 4 },
{ name = "blue robe", chance = 30000 },
{ name = "dreaded cleaver", chance = 30000 },
- { id = 8900, chance = 30000 }, -- heavily rusted shield
+ { name = "slightly rusted shield", chance = 26670 },
{ name = "wand of inferno", chance = 30000 },
{ id = 28341, chance = 1000 }, -- tessellated wall
{ name = "sturdy book", chance = 1000 },
diff --git a/data-otservbr-global/monster/reptiles/adult_goanna.lua b/data-otservbr-global/monster/reptiles/adult_goanna.lua
index da07033e656..956af1034a1 100644
--- a/data-otservbr-global/monster/reptiles/adult_goanna.lua
+++ b/data-otservbr-global/monster/reptiles/adult_goanna.lua
@@ -74,35 +74,49 @@ monster.voices = {
monster.loot = {
{ name = "platinum coin", chance = 100000, maxCount = 3 },
- { name = "envenomed arrow", chance = 55360, maxCount = 8 },
- { name = "earth arrow", chance = 16800, maxCount = 29 },
- { name = "terra rod", chance = 11000 },
- { name = "goanna meat", chance = 12140 },
- { name = "goanna claw", chance = 4290 },
- { name = "lizard heart", chance = 1400 },
- { name = "red goanna scale", chance = 10000 },
- { name = "fur armor", chance = 3200 },
- { name = "serpent sword", chance = 3600 },
- { name = "terra amulet", chance = 4650 },
- { name = "terra hood", chance = 7100 },
- { name = "wood cape", chance = 1800 },
- { name = "scared frog", chance = 2100 },
- { name = "sacred tree amulet", chance = 2500 },
- { name = "small tortoise", chance = 1800 },
+ { name = "envenomed arrow", chance = 60120, maxCount = 8 },
+ { name = "earth arrow", chance = 13180, maxCount = 30 },
+ { name = "emerald bangle", chance = 12240 },
+ { name = "goanna meat", chance = 11650 },
+ { name = "small enchanted emerald", chance = 10030 },
+ { name = "green crystal splinter", chance = 9100 },
+ { name = "terra rod", chance = 8250 },
+ { name = "red goanna scale", chance = 7910 },
+ { name = "blue crystal shard", chance = 7820 },
+ { name = "small sapphire", chance = 6890, maxCount = 2 },
+ { name = "terra hood", chance = 6630 },
+ { name = "goanna claw", chance = 6210 },
+ { name = "terra amulet", chance = 6040 },
+ { name = "yellow gem", chance = 4250 },
+ { name = "silver brooch", chance = 4000 },
+ { name = "green gem", chance = 3150 },
+ { name = "serpent sword", chance = 2810 },
+ { name = "scared frog", chance = 2720 },
+ { name = "opal", chance = 2640, maxCount = 2 },
+ { name = "onyx chip", chance = 2640 },
+ { name = "gemmed figurine", chance = 1530 },
+ { name = "small amethyst", chance = 1360 },
+ { name = "fur armor", chance = 1360 },
+ { name = "wood cape", chance = 1280 },
+ { name = "white pearl", chance = 1280 },
+ { name = "small tortoise", chance = 1190 },
+ { name = "sacred tree amulet", chance = 1020 },
+ { name = "coral brooch", chance = 770 },
+ { name = "lizard heart", chance = 770 },
}
monster.attacks = {
- { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -350, condition = { type = CONDITION_POISON, totalDamage = 19, interval = 4000 } },
- { name = "wave t", interval = 2000, chance = 10, minDamage = -250, maxDamage = -380, target = false },
- { name = "combat", interval = 2000, chance = 12, type = COMBAT_EARTHDAMAGE, minDamage = -450, maxDamage = -550, range = 3, radius = 1, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_EXPLOSIONHIT, target = true },
- { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -210, maxDamage = -300, radius = 5, effect = CONST_ME_GROUNDSHAKER, target = false },
+ { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -400, condition = { type = CONDITION_POISON, totalDamage = 200, interval = 4000 } },
+ { name = "combat", interval = 2500, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -600, range = 3, shootEffect = CONST_ANI_EARTH, effect = CONST_ME_HITBYPOISON, target = true },
+ { name = "combat", interval = 3000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -380, radius = 2, effect = CONST_ME_GROUNDSHAKER, target = false },
+ { name = "combat", interval = 3600, chance = 40, type = COMBAT_EARTHDAMAGE, minDamage = -300, maxDamage = -390, length = 8, spread = 3, effect = CONST_ME_GREEN_RINGS, target = false },
}
monster.defenses = {
defense = 84,
armor = 84,
- mitigation = 2.60,
- { name = "speed", interval = 2000, chance = 5, speedChange = 500, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
+ mitigation = 2.6,
+ { name = "speed", interval = 2000, chance = 15, speedChange = 420, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
}
monster.elements = {
diff --git a/data-otservbr-global/monster/reptiles/boar_man.lua b/data-otservbr-global/monster/reptiles/boar_man.lua
index 53ec742ca22..0d142e332d4 100644
--- a/data-otservbr-global/monster/reptiles/boar_man.lua
+++ b/data-otservbr-global/monster/reptiles/boar_man.lua
@@ -90,10 +90,10 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -498 },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = -375, maxDamage = -392, range = 7, shootEffect = CONST_ANI_THROWINGKNIFE, target = true },
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_DEATHDAMAGE, minDamage = -386, maxDamage = -480, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -60, maxDamage = -140, range = 1, radius = 4, effect = CONST_ME_EXPLOSIONAREA, target = false },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -400, length = 8, spread = 3, effect = CONST_ME_ENERGYHIT, target = false },
+ { name = "combat", interval = 2300, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -375, maxDamage = -392, range = 7, shootEffect = CONST_ANI_THROWINGKNIFE, target = true },
+ { name = "combat", interval = 2600, chance = 40, type = COMBAT_DEATHDAMAGE, minDamage = -386, maxDamage = -480, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
+ { name = "combat", interval = 2900, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -60, maxDamage = -140, range = 1, radius = 4, effect = CONST_ME_EXPLOSIONAREA, target = false },
+ { name = "combat", interval = 3200, chance = 35, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -400, length = 8, spread = 3, effect = CONST_ME_ENERGYHIT, target = false },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/carnivostrich.lua b/data-otservbr-global/monster/reptiles/carnivostrich.lua
index e0e0e362573..e0ebbb811ed 100644
--- a/data-otservbr-global/monster/reptiles/carnivostrich.lua
+++ b/data-otservbr-global/monster/reptiles/carnivostrich.lua
@@ -77,7 +77,7 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 80450, maxCount = 22 },
+ { name = "platinum coin", chance = 80450, maxCount = 28 },
{ name = "small ruby", chance = 16390, maxCount = 8 },
{ name = "small emerald", chance = 8330, maxCount = 8 },
{ name = "strong mana potion", chance = 4910, maxCount = 4 },
diff --git a/data-otservbr-global/monster/reptiles/crape_man.lua b/data-otservbr-global/monster/reptiles/crape_man.lua
index 99d2db01f34..83097403b9c 100644
--- a/data-otservbr-global/monster/reptiles/crape_man.lua
+++ b/data-otservbr-global/monster/reptiles/crape_man.lua
@@ -76,25 +76,25 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 71540, maxCount = 26 },
- { name = "guardian halberd", chance = 5310 },
+ { name = "platinum coin", chance = 71540, maxCount = 28 },
{ name = "crab man claws", chance = 5210, maxCount = 2 },
{ name = "green gem", chance = 3010 },
{ name = "great health potion", chance = 2000, maxCount = 5 },
{ id = 281, chance = 1700 }, -- giant shimmering pearl (green)
- { name = "lightning legs", chance = 1200 },
- { name = "warrior's shield", chance = 1200 },
- { name = "glacier kilt", chance = 1000 },
- { name = "noble axe", chance = 900 },
- { name = "hammer of wrath", chance = 600 },
+ { name = "guardian halberd", chance = 2400 },
+ { name = "lightning legs", chance = 900 },
+ { name = "warrior's shield", chance = 900 },
+ { name = "glacier kilt", chance = 750 },
+ { name = "noble axe", chance = 700 },
+ { name = "hammer of wrath", chance = 400 },
{ name = "ring of the sky", chance = 300 },
}
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -498 },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -320, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
- { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -330, maxDamage = -380, range = 7, radius = 4, shootEffect = CONST_ANI_ENERGYBALL, effect = CONST_ME_PURPLEENERGY, target = true },
- { name = "combat", interval = 2000, chance = 10, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -370, length = 3, spread = 3, effect = CONST_ME_ENERGYHIT, target = false },
+ { name = "combat", interval = 3500, chance = 40, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -320, range = 7, shootEffect = CONST_ANI_SUDDENDEATH, target = true },
+ { name = "combat", interval = 2500, chance = 50, type = COMBAT_ENERGYDAMAGE, minDamage = -330, maxDamage = -380, range = 7, radius = 4, shootEffect = CONST_ANI_ENERGYBALL, effect = CONST_ME_PURPLEENERGY, target = true },
+ { name = "combat", interval = 3000, chance = 65, type = COMBAT_ENERGYDAMAGE, minDamage = -311, maxDamage = -370, length = 3, spread = 3, effect = CONST_ME_ENERGYHIT, target = false },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/fungosaurus.lua b/data-otservbr-global/monster/reptiles/fungosaurus.lua
index d97e91d200d..f7e65e7eb9d 100644
--- a/data-otservbr-global/monster/reptiles/fungosaurus.lua
+++ b/data-otservbr-global/monster/reptiles/fungosaurus.lua
@@ -65,8 +65,8 @@ monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 300, maxDamage = -801 },
{ name = "combat", interval = 3000, chance = 47, type = COMBAT_PHYSICALDAMAGE, minDamage = -800, maxDamage = -1500, effect = CONST_ME_YELLOWSMOKE, target = true },
{ name = "combat", interval = 4000, chance = 31, type = COMBAT_LIFEDRAIN, minDamage = -800, maxDamage = -1500, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false },
- { name = "root", interval = 2000, chance = 1, target = true },
- { name = "fear", interval = 2000, chance = 1, target = true },
+ { name = "root", interval = 2000, chance = 3, target = true },
+ { name = "fear", interval = 2000, chance = 3, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/harpy.lua b/data-otservbr-global/monster/reptiles/harpy.lua
index 01108f33336..cf146658994 100644
--- a/data-otservbr-global/monster/reptiles/harpy.lua
+++ b/data-otservbr-global/monster/reptiles/harpy.lua
@@ -76,18 +76,18 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 73130, maxCount = 30 },
+ { name = "platinum coin", chance = 73130, maxCount = 25 },
{ name = "harpy feathers", chance = 6720 },
{ name = "violet crystal shard", chance = 4690 },
{ name = "blue crystal shard", chance = 4530 },
{ name = "great spirit potion", chance = 2970, maxCount = 3 },
- { name = "violet gem", chance = 2500 },
{ name = "gold ring", chance = 1720 },
{ name = "wand of defiance", chance = 1720 },
{ name = "focus cape", chance = 1560 },
- { name = "ornate crossbow", chance = 1410 },
- { name = "magic plate armor", chance = 940 },
+ { name = "violet gem", chance = 1200 },
+ { name = "ornate crossbow", chance = 500 },
{ name = "shockwave amulet", chance = 470 },
+ { name = "magic plate armor", chance = 440 },
}
monster.attacks = {
diff --git a/data-otservbr-global/monster/reptiles/liodile.lua b/data-otservbr-global/monster/reptiles/liodile.lua
index d7f79621679..b8021eea69b 100644
--- a/data-otservbr-global/monster/reptiles/liodile.lua
+++ b/data-otservbr-global/monster/reptiles/liodile.lua
@@ -75,7 +75,7 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 80540, maxCount = 18 },
+ { name = "platinum coin", chance = 80540, maxCount = 23 },
{ name = "small sapphire", chance = 9790, maxCount = 4 },
{ name = "green crystal shard", chance = 5360 },
{ name = "liodile fang", chance = 4030, maxCount = 3 },
@@ -89,8 +89,8 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500 },
- { name = "combat", interval = 2000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -325, maxDamage = -400, range = 7, shootEffect = CONST_ANI_POISONARROW, target = true },
- { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -300, maxDamage = -400, range = 2, effect = CONST_ME_GROUNDSHAKER, target = true },
+ { name = "combat", interval = 2000, chance = 50, type = COMBAT_EARTHDAMAGE, minDamage = -325, maxDamage = -400, range = 7, shootEffect = CONST_ANI_POISONARROW, target = true },
+ { name = "combat", interval = 2000, chance = 34, type = COMBAT_PHYSICALDAMAGE, minDamage = -300, maxDamage = -400, range = 2, effect = CONST_ME_GROUNDSHAKER, target = true },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/naga_archer.lua b/data-otservbr-global/monster/reptiles/naga_archer.lua
index d105ddbb0fc..e8665ed210a 100644
--- a/data-otservbr-global/monster/reptiles/naga_archer.lua
+++ b/data-otservbr-global/monster/reptiles/naga_archer.lua
@@ -53,7 +53,7 @@ monster.flags = {
canPushItems = true,
canPushCreatures = true,
staticAttackChance = 90,
- targetDistance = 4,
+ targetDistance = 3,
runHealth = 0,
healthHidden = false,
isBlockable = false,
@@ -74,28 +74,29 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 13 },
+ { name = "platinum coin", chance = 100000, maxCount = 17 },
{ name = "naga archer scales", chance = 15050, maxCount = 3 },
- { name = "naga earring", chance = 12850 },
- { name = "naga armring", chance = 5960 },
+ { name = "naga earring", chance = 12850, maxCount = 3 },
+ { name = "naga armring", chance = 5960, maxCount = 3 },
{ id = 3007, chance = 5330 }, -- crystal ring
{ name = "hunting spear", chance = 3760 },
{ name = "crossbow", chance = 3130 },
{ name = "blue crystal shard", chance = 1880 },
{ name = "bow", chance = 1570 },
- { name = "elvish bow", chance = 1250 },
+ { name = "elvish bow", chance = 750 },
{ name = "ornate crossbow", chance = 630 },
+ { name = "crystal crossbow", chance = 420 },
{ id = 7441, chance = 630 }, -- ice cube
- { name = "emerald bangle", chance = 630 },
+ { name = "emerald bangle", chance = 930 },
{ name = "silver brooch", chance = 310 },
}
monster.attacks = {
- { name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -95, maxDamage = -390, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- basic_attack
- { name = "nagadeathattack", interval = 2000, chance = 25, minDamage = -430, maxDamage = -505, range = 6, target = true }, -- death_strike
- { name = "nagadeath", interval = 2000, chance = 25, minDamage = -380, maxDamage = -470, target = false }, -- short_death_wave
- { name = "death chain", interval = 2000, chance = 25, minDamage = -460, maxDamage = -520, range = 6, target = true }, -- death_chain
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_PHYSICALDAMAGE, minDamage = -85, maxDamage = -190, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- explosion_strike
+ { name = "combat", interval = 2000, chance = 50, type = COMBAT_PHYSICALDAMAGE, minDamage = -95, maxDamage = -390, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- basic_attack
+ { name = "nagadeathattack", interval = 2500, chance = 20, minDamage = -430, maxDamage = -505, range = 6, target = true }, -- death_strike
+ { name = "nagadeath", interval = 3000, chance = 20, minDamage = -380, maxDamage = -470, target = false }, -- short_death_wave
+ { name = "death chain", interval = 3500, chance = 20, minDamage = -460, maxDamage = -520, range = 6, target = true }, -- death_chain
+ { name = "combat", interval = 4000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -85, maxDamage = -190, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_PURPLEENERGY, range = 6, target = true }, -- explosion_strike
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/naga_warrior.lua b/data-otservbr-global/monster/reptiles/naga_warrior.lua
index 32e24e4bb53..485e345c74d 100644
--- a/data-otservbr-global/monster/reptiles/naga_warrior.lua
+++ b/data-otservbr-global/monster/reptiles/naga_warrior.lua
@@ -74,28 +74,28 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 12 },
+ { name = "platinum coin", chance = 100000, maxCount = 20 },
{ name = "dagger", chance = 38810 },
{ name = "strong health potion", chance = 14930, maxCount = 2 },
- { name = "naga warrior scales", chance = 10600, maxCount = 4 },
- { name = "naga earring", chance = 6420, maxCount = 2 },
+ { name = "naga warrior scales", chance = 10600, maxCount = 3 },
+ { name = "naga earring", chance = 6420, maxCount = 3 },
{ id = 3307, chance = 5520 }, -- scimitar
{ name = "naga armring", chance = 3730 },
{ name = "plate armor", chance = 2990 },
{ name = "spiky club", chance = 2090 },
{ name = "serpent sword", chance = 1940 },
- { name = "violet crystal shard", chance = 1640 },
+ { name = "violet crystal shard", chance = 2640 },
{ name = "katana", chance = 1490 },
- { name = "relic sword", chance = 1190 },
- { name = "knight armor", chance = 450 },
+ { name = "relic sword", chance = 600 },
+ { name = "knight armor", chance = 1100 },
{ id = 7441, chance = 300 }, -- ice cube
}
monster.attacks = {
{ name = "combat", interval = 2000, chance = 100, type = COMBAT_PHYSICALDAMAGE, minDamage = -120, maxDamage = -340, target = true }, -- basic_attack
- { name = "combat", interval = 2000, chance = 25, type = COMBAT_PHYSICALDAMAGE, minDamage = -320, maxDamage = -430, effect = CONST_ME_YELLOWSMOKE, range = 3, target = true }, -- eruption_strike
- { name = "nagadeathattack", interval = 2000, chance = 25, minDamage = -360, maxDamage = -415, target = true }, -- death_strike
- { name = "combat", interval = 4000, chance = 31, type = COMBAT_LIFEDRAIN, minDamage = -360, maxDamage = -386, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false }, -- great_blood_ball
+ { name = "combat", interval = 2500, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -320, maxDamage = -430, effect = CONST_ME_YELLOWSMOKE, range = 3, target = true }, -- eruption_strike
+ { name = "nagadeathattack", interval = 3000, chance = 35, minDamage = -360, maxDamage = -415, target = true }, -- death_strike
+ { name = "combat", interval = 3500, chance = 35, type = COMBAT_LIFEDRAIN, minDamage = -360, maxDamage = -386, radius = 4, effect = CONST_ME_DRAWBLOOD, target = false }, -- great_blood_ball
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/reptiles/rhindeer.lua b/data-otservbr-global/monster/reptiles/rhindeer.lua
index 743d9fb492a..4cd428e94f1 100644
--- a/data-otservbr-global/monster/reptiles/rhindeer.lua
+++ b/data-otservbr-global/monster/reptiles/rhindeer.lua
@@ -76,18 +76,18 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 72260, maxCount = 31 },
- { name = "brown crystal splinter", chance = 11550, maxCount = 7 },
+ { name = "platinum coin", chance = 72260, maxCount = 30 },
+ { name = "brown crystal splinter", chance = 11550, maxCount = 4 },
{ name = "rhindeer antlers", chance = 6020 },
{ name = "rainbow quartz", chance = 4940, maxCount = 2 },
- { name = "violet gem", chance = 4050 },
{ name = "great mana potion", chance = 2670, maxCount = 4 },
{ name = "titan axe", chance = 2470 },
{ name = "yellow gem", chance = 1880 },
{ name = "knight armor", chance = 1380 },
+ { name = "violet gem", chance = 1200 },
{ id = 23543, chance = 890 }, -- collar of green plasma
- { name = "heavy mace", chance = 890 },
- { name = "mastermind shield", chance = 690 },
+ { name = "heavy mace", chance = 300 },
+ { name = "mastermind shield", chance = 400 },
{ id = 3053, chance = 690 }, -- time ring
}
diff --git a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua
index 89d8b24c975..2d1c2090b37 100644
--- a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua
+++ b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua
@@ -65,24 +65,24 @@ monster.voices = {
}
monster.loot = {
- { name = "platinum coin", chance = 100000, maxCount = 80 },
+ { name = "platinum coin", chance = 100000, maxCount = 8 },
{ name = "great health potion", chance = 15701 },
- { name = "two-headed turtle heads", chance = 15000 },
+ { name = "two-headed turtle heads", chance = 8700 },
{ name = "strong mana potion", chance = 13373 },
- { name = "hydrophytes", chance = 9552 },
+ { name = "hydrophytes", chance = 11000 },
{ id = 1047, chance = 6388 }, -- bone
- { name = "glacier shoes", chance = 6239 },
+ { name = "glacier shoes", chance = 4650 },
{ id = 281, chance = 3582 }, -- giant shimmering pearl (green)
{ name = "small tropical fish", chance = 3582 },
- { name = "coral brooch", chance = 3343 },
+ { name = "coral brooch", chance = 2600 },
{ name = "silver brooch", chance = 2507 },
- { name = "lightning headband", chance = 6448 },
- { name = "knight legs", chance = 7269 },
+ { name = "lightning headband", chance = 2110 },
+ { name = "knight legs", chance = 2000 },
{ name = "gemmed figurine", chance = 2090 },
{ name = "emerald bangle", chance = 1373 },
{ name = "terra amulet", chance = 1373 },
{ id = 3040, chance = 1313 }, -- "gold nugget"
- { name = "spellbook of enlightenment", chance = 6134 },
+ { name = "spellbook of enlightenment", chance = 1300 },
{ id = 3565, chance = 1015 }, -- "cape"
{ id = 10422, chance = 657 }, -- "clay lump"
{ name = "white gem", chance = 418 },
diff --git a/data-otservbr-global/monster/reptiles/young_goanna.lua b/data-otservbr-global/monster/reptiles/young_goanna.lua
index 5f68752fb46..ad4b0c01128 100644
--- a/data-otservbr-global/monster/reptiles/young_goanna.lua
+++ b/data-otservbr-global/monster/reptiles/young_goanna.lua
@@ -74,23 +74,33 @@ monster.voices = {
monster.loot = {
{ name = "platinum coin", chance = 100000, maxCount = 3 },
- { name = "envenomed arrow", chance = 68000, maxCount = 35 },
- { name = "terra rod", chance = 10900 },
- { name = "goanna meat", chance = 9800 },
- { name = "snakebite rod", chance = 9000 },
- { name = "blue goanna scale", chance = 7900 },
- { name = "goanna claw", chance = 4300 },
- { name = "serpent sword", chance = 4000 },
- { name = "leaf star", chance = 3800, maxCount = 3 },
- { name = "silver amulet", chance = 3800 },
- { name = "springsprout rod", chance = 2700 },
- { name = "scared frog", chance = 2100 },
- { name = "terra amulet", chance = 1100 },
- { name = "lizard heart", chance = 800 },
- { name = "sacred tree amulet", chance = 800 },
- { name = "small tortoise", chance = 550 },
- { name = "fur armor", chance = 270 },
- { name = "terra hood", chance = 250 },
+ { name = "envenomed arrow", chance = 70400, maxCount = 35 },
+ { name = "snakebite rod", chance = 10620 },
+ { name = "goanna meat", chance = 10030 },
+ { name = "blue crystal shard", chance = 9110 },
+ { name = "terra rod", chance = 8940 },
+ { name = "blue goanna scale", chance = 8260 },
+ { name = "small enchanted emerald", chance = 4890 },
+ { name = "leaf star", chance = 4550, maxCount = 3 },
+ { name = "rainbow quartz", chance = 4050, maxCount = 3 },
+ { name = "onyx chip", chance = 4050 },
+ { name = "goanna claw", chance = 3880 },
+ { name = "violet gem", chance = 3540 },
+ { name = "serpent sword", chance = 3370 },
+ { name = "springsprout rod", chance = 3370 },
+ { name = "green crystal shard", chance = 2950 },
+ { name = "scared frog", chance = 2610 },
+ { name = "yellow gem", chance = 2530 },
+ { name = "silver amulet", chance = 2280 },
+ { name = "terra amulet", chance = 1430 },
+ { name = "blue gem", chance = 1180 },
+ { name = "terra hood", chance = 1100 },
+ { name = "blue crystal splinter", chance = 1010 },
+ { name = "sacred tree amulet", chance = 840 },
+ { name = "small tortoise", chance = 670 },
+ { name = "lizard heart", chance = 590 },
+ { name = "wooden spellbook", chance = 170 },
+ { name = "fur armor", chance = 80 },
}
monster.attacks = {
@@ -104,7 +114,7 @@ monster.defenses = {
defense = 78,
armor = 78,
mitigation = 2.16,
- { name = "speed", interval = 2000, chance = 5, speedChange = 350, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = 420, effect = CONST_ME_MAGIC_RED, target = false, duration = 5000 },
}
monster.elements = {
diff --git a/data-otservbr-global/monster/trainers/training_machine.lua b/data-otservbr-global/monster/trainers/training_machine.lua
index cb50c0a4532..93d042efa5e 100644
--- a/data-otservbr-global/monster/trainers/training_machine.lua
+++ b/data-otservbr-global/monster/trainers/training_machine.lua
@@ -58,7 +58,18 @@ monster.defenses = {
{ name = "combat", type = COMBAT_HEALING, chance = 15, interval = 2000, minDamage = 10000, maxDamage = 50000, effect = CONST_ME_MAGIC_BLUE },
}
-monster.elements = {}
+monster.elements = {
+ { type = COMBAT_PHYSICALDAMAGE, percent = 0 },
+ { type = COMBAT_ENERGYDAMAGE, percent = 0 },
+ { type = COMBAT_EARTHDAMAGE, percent = 0 },
+ { type = COMBAT_FIREDAMAGE, percent = 0 },
+ { type = COMBAT_LIFEDRAIN, percent = 0 },
+ { type = COMBAT_MANADRAIN, percent = 0 },
+ { type = COMBAT_DROWNDAMAGE, percent = 0 },
+ { type = COMBAT_ICEDAMAGE, percent = 0 },
+ { type = COMBAT_HOLYDAMAGE, percent = 0 },
+ { type = COMBAT_DEATHDAMAGE, percent = 0 },
+}
monster.immunities = {}
diff --git a/data-otservbr-global/monster/undeads/lost_soul.lua b/data-otservbr-global/monster/undeads/lost_soul.lua
index d00e9eb9d18..99002595639 100644
--- a/data-otservbr-global/monster/undeads/lost_soul.lua
+++ b/data-otservbr-global/monster/undeads/lost_soul.lua
@@ -103,7 +103,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -420 },
{ name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -40, maxDamage = -210, length = 3, spread = 0, effect = CONST_ME_MAGIC_RED, target = false },
- { name = "speed", interval = 2000, chance = 20, speedChange = -800, radius = 6, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 4000 },
+ { name = "speed", interval = 2000, chance = 20, speedChange = -100, radius = 6, effect = CONST_ME_SMALLCLOUDS, target = false, duration = 4000 },
}
monster.defenses = {
diff --git a/data-otservbr-global/monster/vermins/crystal_spider.lua b/data-otservbr-global/monster/vermins/crystal_spider.lua
index ecb4b9ee62c..b6dd122b188 100644
--- a/data-otservbr-global/monster/vermins/crystal_spider.lua
+++ b/data-otservbr-global/monster/vermins/crystal_spider.lua
@@ -100,7 +100,7 @@ monster.loot = {
monster.attacks = {
{ name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -250, condition = { type = CONDITION_POISON, totalDamage = 160, interval = 4000 } },
- { name = "speed", interval = 2000, chance = 15, speedChange = -800, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 },
+ { name = "speed", interval = 2000, chance = 15, speedChange = -100, range = 7, radius = 6, effect = CONST_ME_POFF, target = false, duration = 15000 },
{ name = "combat", interval = 2000, chance = 15, type = COMBAT_ICEDAMAGE, minDamage = -50, maxDamage = -100, range = 7, shootEffect = CONST_ANI_ICE, effect = CONST_ME_ICEAREA, target = true },
{ name = "speed", interval = 2000, chance = 20, speedChange = -600, range = 7, shootEffect = CONST_ANI_SNOWBALL, target = true, duration = 10000 },
}
diff --git a/data-otservbr-global/monster/vermins/diremaw.lua b/data-otservbr-global/monster/vermins/diremaw.lua
index 1e3accb0fd3..b89c47c99e7 100644
--- a/data-otservbr-global/monster/vermins/diremaw.lua
+++ b/data-otservbr-global/monster/vermins/diremaw.lua
@@ -93,7 +93,7 @@ monster.loot = {
{ name = "gold ingot", chance = 2970 },
{ id = 281, chance = 3100 }, -- giant shimmering pearl (green)
{ name = "suspicious device", chance = 600 },
- { name = "mycological bow", chance = 1200 },
+ { name = "mycological bow", chance = 200 },
{ name = "mushroom backpack", chance = 1500 },
}
diff --git a/data-otservbr-global/npc/hireling.lua b/data-otservbr-global/npc/hireling.lua
index 11757bff0a5..ca72e1c5bcf 100644
--- a/data-otservbr-global/npc/hireling.lua
+++ b/data-otservbr-global/npc/hireling.lua
@@ -656,7 +656,7 @@ function createHirelingType(HirelingName)
end
elseif npcHandler:getTopic(playerId) == TOPIC.BANK then
enableBankSystem[playerId] = true
- elseif npcHandler:getTopic(playerId) == TOPIC.FOOD then
+ elseif npcHandler:getTopic(playerId) == TOPIC.FOOD or npcHandler:getTopic(playerId) == TOPIC_FOOD.SKILL_CHOOSE then
handleFoodActions(npc, creature, message)
elseif npcHandler:getTopic(playerId) == TOPIC.GOODS then
-- Ensures players cannot access other shop categories
diff --git a/data-otservbr-global/raids/raids.xml b/data-otservbr-global/raids/raids.xml
index 035aab91e6c..51aae3a0165 100644
--- a/data-otservbr-global/raids/raids.xml
+++ b/data-otservbr-global/raids/raids.xml
@@ -14,68 +14,41 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
@@ -85,14 +58,12 @@
-
-
@@ -103,10 +74,9 @@
-
+
-
diff --git a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua
index a73caac912e..ad737b276d3 100644
--- a/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua
+++ b/data-otservbr-global/scripts/actions/adventurers_guild/adventurers_stone.lua
@@ -60,7 +60,7 @@ end
function adventurersStone.onUse(player, item, fromPosition, target, toPosition, isHotkey)
local tile = Tile(player:getPosition())
- if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or tile:hasFlag(TILESTATE_HOUSE) or player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT) then
+ if not tile:hasFlag(TILESTATE_PROTECTIONZONE) or tile:hasFlag(TILESTATE_HOUSE) or player:isPzLocked() then
doNotTeleport(player)
return false
end
diff --git a/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua
new file mode 100644
index 00000000000..a5300abb8f3
--- /dev/null
+++ b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/ahau_lever.lua
@@ -0,0 +1,24 @@
+local config = {
+ boss = {
+ name = "Ahau",
+ position = Position(34008, 31696, 10),
+ },
+ timeAfterKill = 60,
+ playerPositions = {
+ { pos = Position(34037, 31714, 10), teleport = Position(34008, 31703, 10) },
+ { pos = Position(34036, 31714, 10), teleport = Position(34008, 31703, 10) },
+ { pos = Position(34035, 31714, 10), teleport = Position(34008, 31703, 10) },
+ { pos = Position(34034, 31714, 10), teleport = Position(34008, 31703, 10) },
+ { pos = Position(34033, 31714, 10), teleport = Position(34008, 31703, 10) },
+ },
+ specPos = {
+ from = Position(33999, 31692, 10),
+ to = Position(34018, 31705, 10),
+ },
+ exit = Position(34036, 31717, 10),
+ exitTeleporter = Position(34002, 31706, 10),
+}
+
+local lever = BossLever(config)
+lever:position(Position(34038, 31714, 10))
+lever:register()
diff --git a/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua
new file mode 100644
index 00000000000..e48a5a477d4
--- /dev/null
+++ b/data-otservbr-global/scripts/actions/quests/adventures_of_galthen/idol_of_tukh.lua
@@ -0,0 +1,31 @@
+local config = {
+ [40578] = {
+ female = 1598,
+ male = 1597,
+ msg = "ancient aucar",
+ },
+}
+
+local idol = Action()
+function idol.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ local choice = config[item.itemid]
+ if not choice then
+ return true
+ end
+
+ if not player:hasOutfit(player:getSex() == PLAYERSEX_FEMALE and choice.female or choice.male) then
+ player:addOutfit(choice.female)
+ player:addOutfit(choice.male)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have received the " .. choice.msg .. " outfit!")
+ player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN)
+ item:remove(1)
+ else
+ player:sendCancelMessage("You have already obtained this outfit!")
+ end
+ return true
+end
+
+for k, v in pairs(config) do
+ idol:id(k)
+end
+idol:register()
diff --git a/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua
new file mode 100644
index 00000000000..d76538b4236
--- /dev/null
+++ b/data-otservbr-global/scripts/actions/quests/feaster_of_souls/portal_brain_head.lua
@@ -0,0 +1,106 @@
+local config = {
+ bossName = "Brain Head",
+ requiredLevel = 250,
+ timeToFightAgain = 10, -- In hour
+ destination = Position(31963, 32324, 10),
+ exitPosition = Position(31971, 32325, 10),
+}
+
+local zone = Zone("boss." .. toKey(config.bossName))
+local encounter = Encounter("Brain Head", {
+ zone = zone,
+ timeToSpawnMonsters = "50ms",
+})
+
+zone:blockFamiliars()
+zone:setRemoveDestination(config.exitPosition)
+
+local locked = false
+
+function encounter:onReset()
+ locked = false
+ encounter:removeMonsters()
+end
+
+encounter:addRemoveMonsters():autoAdvance()
+encounter:addBroadcast("You've entered the Brain Head's lair."):autoAdvance()
+encounter
+ :addSpawnMonsters({
+ {
+ name = "Brain Head",
+ positions = {
+ Position(31954, 32325, 10),
+ },
+ },
+ {
+ name = "Cerebellum",
+ positions = {
+ Position(31953, 32324, 10),
+ Position(31955, 32324, 10),
+ Position(31953, 32326, 10),
+ Position(31955, 32326, 10),
+ Position(31960, 32320, 10),
+ Position(31960, 32330, 10),
+ Position(31947, 32320, 10),
+ Position(31947, 32330, 10),
+ },
+ },
+ })
+ :autoAdvance("30s")
+
+encounter
+ :addStage({
+ start = function()
+ locked = true
+ end,
+ })
+ :autoAdvance("270s")
+
+encounter:addRemovePlayers():autoAdvance()
+
+encounter:startOnEnter()
+encounter:register()
+
+local teleportBoss = MoveEvent()
+function teleportBoss.onStepIn(creature, item, position, fromPosition)
+ if not creature or not creature:isPlayer() then
+ return false
+ end
+ local player = creature
+ if player:getLevel() < config.requiredLevel then
+ player:teleportTo(fromPosition, true)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to be level " .. config.requiredLevel .. " or higher.")
+ return true
+ end
+ if locked then
+ player:teleportTo(fromPosition, true)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. config.bossName .. ".")
+ return false
+ end
+ if zone:countPlayers(IgnoredByMonsters) >= 5 then
+ player:teleportTo(fromPosition, true)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The boss room is full.")
+ return false
+ end
+ local timeLeft = player:getBossCooldown(config.bossName) - os.time()
+ if timeLeft > 0 then
+ player:teleportTo(fromPosition, true)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait " .. getTimeInWords(timeLeft) .. " to face " .. config.bossName .. " again!")
+ player:getPosition():sendMagicEffect(CONST_ME_POFF)
+ return false
+ end
+ player:teleportTo(config.destination)
+ player:getPosition():sendMagicEffect(CONST_ME_TELEPORT)
+ player:setBossCooldown(config.bossName, os.time() + config.timeToFightAgain * 3600)
+ player:sendBosstiaryCooldownTimer()
+end
+
+teleportBoss:aid(30407)
+teleportBoss:type("stepin")
+teleportBoss:register()
+
+SimpleTeleport(Position(31946, 32334, 10), config.exitPosition)
diff --git a/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua b/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua
index b37d681bce9..bc92b403adf 100644
--- a/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua
+++ b/data-otservbr-global/scripts/actions/quests/forgotten_knowledge/time_machine.lua
@@ -1,13 +1,11 @@
-local forgottenKnowledgeMachine = Action()
-function forgottenKnowledgeMachine.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+local timeMachine = Action()
+function timeMachine.onUse(player, item, fromPosition, target, toPosition, isHotkey)
if player:getPosition() == Position(32870, 32723, 15) then
player:teleportTo(Position(32870, 32724, 14))
player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.")
return true
- end
-
- if player:getPosition() == Position(32870, 32723, 14) then
+ elseif player:getPosition() == Position(32870, 32723, 14) then
if player:canFightBoss("The Time Guardian") then
player:teleportTo(Position(32870, 32724, 15))
player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT)
@@ -17,10 +15,22 @@ function forgottenKnowledgeMachine.onUse(player, item, fromPosition, target, toP
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait a while before travel in time!")
return true
end
- else
- return false
end
+
+ if player:getPosition() == Position(33453, 31029, 8) then
+ player:teleportTo(Position(32430, 32167, 8))
+ player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.")
+ return true
+ elseif player:getPosition() == Position(32430, 32166, 8) then
+ player:teleportTo(Position(33453, 31030, 8))
+ player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The mechanism takes you back in time.")
+ return true
+ end
+
+ return false
end
-forgottenKnowledgeMachine:id(25096)
-forgottenKnowledgeMachine:register()
+timeMachine:id(25096)
+timeMachine:register()
diff --git a/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua b/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua
new file mode 100644
index 00000000000..070b46054a6
--- /dev/null
+++ b/data-otservbr-global/scripts/creaturescripts/monster/invulnerable.lua
@@ -0,0 +1,18 @@
+local invulnerable = CreatureEvent("monster.invulnerable")
+function invulnerable.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
+ if not creature then
+ return primaryDamage, primaryType, secondaryDamage, secondaryType
+ end
+ return false
+end
+invulnerable:register()
+
+function Monster:setInvulnerable()
+ self:registerEvent("monster.invulnerable")
+ return true
+end
+
+function Monster:removeInvulnerable()
+ self:unregisterEvent("monster.invulnerable")
+ return true
+end
diff --git a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua
index 2f570a72e3e..de3b9cc4f0a 100644
--- a/data-otservbr-global/scripts/creaturescripts/others/login_events.lua
+++ b/data-otservbr-global/scripts/creaturescripts/others/login_events.lua
@@ -10,7 +10,6 @@ function loginEvents.onLogin(player)
"FamiliarAdvance",
--Quests
--Cults Of Tibia Quest
- "LeidenHeal",
"HealthPillar",
"YalahariHealth",
}
diff --git a/data-otservbr-global/scripts/creaturescripts/players/namelock.lua b/data-otservbr-global/scripts/creaturescripts/players/namelock.lua
new file mode 100644
index 00000000000..c34eab6f181
--- /dev/null
+++ b/data-otservbr-global/scripts/creaturescripts/players/namelock.lua
@@ -0,0 +1,21 @@
+function CheckNamelock(player)
+ local namelockReason = player:kv():get("namelock")
+ if not namelockReason then
+ return true
+ end
+ player:setMoveLocked(true)
+ player:teleportTo(player:getTown():getTemplePosition())
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Your name has been locked for the following reason: " .. namelockReason .. ".")
+ player:openStore("extras")
+ addPlayerEvent(sendRequestPurchaseData, 50, player, 65002, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
+ addPlayerEvent(CheckNamelock, 30000, player)
+end
+
+local playerLogin = CreatureEvent("NamelockLogin")
+
+function playerLogin.onLogin(player)
+ addPlayerEvent(CheckNamelock, 1000, player)
+ return true
+end
+
+playerLogin:register()
diff --git a/data-otservbr-global/scripts/hazard/primal.lua b/data-otservbr-global/scripts/hazard/primal.lua
index 2b8fd67a25d..ef9a9c31ca5 100644
--- a/data-otservbr-global/scripts/hazard/primal.lua
+++ b/data-otservbr-global/scripts/hazard/primal.lua
@@ -7,6 +7,7 @@ local hazard = Hazard.new({
crit = true,
dodge = true,
damageBoost = true,
+ defenseBoost = true,
})
hazard:register()
diff --git a/data-otservbr-global/scripts/lib/register_monster_type.lua b/data-otservbr-global/scripts/lib/register_monster_type.lua
index 49537ac9c4c..c62ca0281d7 100644
--- a/data-otservbr-global/scripts/lib/register_monster_type.lua
+++ b/data-otservbr-global/scripts/lib/register_monster_type.lua
@@ -472,20 +472,40 @@ registerMonsterType.loot = function(mtype, mask)
end
end
end
+local playerElements = { COMBAT_PHYSICALDAMAGE, COMBAT_ENERGYDAMAGE, COMBAT_EARTHDAMAGE, COMBAT_FIREDAMAGE, COMBAT_ICEDAMAGE, COMBAT_HOLYDAMAGE, COMBAT_DEATHDAMAGE }
registerMonsterType.elements = function(mtype, mask)
+ local min = configManager.getNumber(configKeys.MIN_ELEMENTAL_RESISTANCE)
+ local max = configManager.getNumber(configKeys.MAX_ELEMENTAL_RESISTANCE)
+ local canClip = false
if type(mask.elements) == "table" then
+ for _, playerElement in pairs(playerElements) do
+ local found = false
+ for _, element in pairs(mask.elements) do
+ if element.type == playerElement then
+ found = true
+ canClip = canClip or element.percent ~= 100
+ break
+ end
+ end
+ canClip = canClip or not found
+ end
for _, element in pairs(mask.elements) do
if element.type and element.percent then
- mtype:addElement(element.type, element.percent)
+ local value = element.percent
+ if canClip then
+ value = math.min(math.max(element.percent, min), max)
+ end
+ mtype:addElement(element.type, value)
end
end
end
end
registerMonsterType.reflects = function(mtype, mask)
+ local max = configManager.getNumber(configKeys.MAX_DAMAGE_REFLECTION)
if type(mask.reflects) == "table" then
for _, reflect in pairs(mask.reflects) do
if reflect.type and reflect.percent then
- mtype:addReflect(reflect.type, reflect.percent)
+ mtype:addReflect(reflect.type, math.min(reflect.percent, max))
end
end
end
diff --git a/data-otservbr-global/scripts/movements/teleport/oskayaat.lua b/data-otservbr-global/scripts/movements/teleport/oskayaat.lua
new file mode 100644
index 00000000000..4a5ac5683ac
--- /dev/null
+++ b/data-otservbr-global/scripts/movements/teleport/oskayaat.lua
@@ -0,0 +1,33 @@
+SimpleTeleport(Position(33028, 32953, 8), Position(33042, 32950, 9))
+SimpleTeleport(Position(33043, 32950, 9), Position(33028, 32952, 8))
+
+local wallTeleport = Action()
+function wallTeleport.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ if player:getPosition().y < 32915 then
+ player:teleportTo(Position(33038, 32916, 9))
+ else
+ player:teleportTo(Position(33038, 32914, 9))
+ end
+ return true
+end
+
+wallTeleport:position(Position(33038, 32915, 9))
+wallTeleport:register()
+
+local boatExit = Action()
+function boatExit.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ player:teleportTo(Position(33176, 32882, 7))
+ return true
+end
+
+boatExit:position(Position(33069, 32915, 7))
+boatExit:register()
+
+local boatEntry = Action()
+function boatEntry.onUse(player, item, fromPosition, target, toPosition, isHotkey)
+ player:teleportTo(Position(33069, 32916, 7))
+ return true
+end
+
+boatEntry:position(Position(33176, 32883, 7))
+boatEntry:register()
diff --git a/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua
new file mode 100644
index 00000000000..ac10eead616
--- /dev/null
+++ b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_fight.lua
@@ -0,0 +1,234 @@
+local bossZone = Zone("boss.the-monster")
+
+local puddleId = 42075
+local jailBarsId = 2184
+
+local encounter = Encounter("The Monster", {
+ zone = bossZone,
+ timeToSpawnMonsters = "10ms",
+})
+
+local function freeMonster()
+ local tile = Tile(Position(33844, 32591, 12))
+ if tile then
+ while true do
+ local item = tile:getItemById(jailBarsId)
+ if item then
+ item:remove()
+ else
+ break
+ end
+ end
+ end
+end
+
+function encounter:onReset(position)
+ encounter:removeMonsters()
+ freeMonster()
+end
+
+encounter:addRemoveMonsters():autoAdvance()
+encounter
+ :addStage({
+ start = function()
+ Game.createItem(jailBarsId, 1, Position(33844, 32591, 12))
+ end,
+ })
+ :autoAdvance()
+
+encounter:addSpawnMonsters({
+ {
+ name = "Doctor Marrow",
+ event = "fight.the-monster.DoctorMarrowHealth",
+ positions = {
+ Position(33838, 32591, 12),
+ },
+ spawn = function(monster)
+ monster:setInvulnerable()
+ end,
+ },
+ {
+ name = "The Monster",
+ event = { "fight.the-monster.TheMonsterHealth", "fight.the-monster.TheMonsterDeath" },
+ positions = {
+ Position(33845, 32591, 12),
+ },
+ spawn = function(monster)
+ monster:setIcon("the-monster", CreatureIconCategory_Quests, CreatureIconQuests_PurpleShield, 20)
+ end,
+ },
+ {
+ name = "Antenna",
+ event = "fight.the-monster.AntennaDeath",
+ positions = {
+ Position(33834, 32589, 12),
+ Position(33840, 32589, 12),
+ Position(33834, 32593, 12),
+ Position(33840, 32593, 12),
+ },
+ },
+})
+
+encounter:addStage({
+ start = function()
+ local monsters = encounter:getZone():getMonstersByName("Doctor Marrow")
+ if not monsters or #monsters == 0 then
+ return false
+ end
+ local doctor = monsters[1]
+ doctor:removeInvulnerable()
+ end,
+})
+
+encounter:addStage({
+ start = function()
+ freeMonster()
+ end,
+})
+
+encounter:register()
+
+local spawnContainers = GlobalEvent("fight.the-monster.containers.onThink")
+function spawnContainers.onThink(interval, lastExecution)
+ return true
+end
+
+spawnContainers:interval(4000)
+spawnContainers:register()
+
+local doctorHealth = CreatureEvent("fight.the-monster.DoctorMarrowHealth")
+function doctorHealth.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
+ if not creature then
+ return primaryDamage, primaryType, secondaryDamage, secondaryType
+ end
+ local newHealth = creature:getHealth() - primaryDamage - secondaryDamage
+ if newHealth <= creature:getMaxHealth() * 0.5 then
+ creature:setHealth(creature:getMaxHealth())
+ creature:remove()
+ encounter:nextStage()
+ return false
+ end
+ return primaryDamage, primaryType, secondaryDamage, secondaryType
+end
+
+doctorHealth:register()
+
+local antennaDeath = CreatureEvent("fight.the-monster.AntennaDeath")
+function antennaDeath.onDeath()
+ -- The monster count is only updated AFTER the event is called, so we need to subtract 1
+ local count = encounter:countMonsters("antenna") - 1
+ if count == 0 then
+ encounter:nextStage()
+ end
+end
+
+antennaDeath:register()
+
+local alchemistContainerDeath = CreatureEvent("fight.the-monster.AlchemistContainerDeath")
+function alchemistContainerDeath.onDeath(creature)
+ local directions = { DIRECTION_NORTH, DIRECTION_EAST, DIRECTION_SOUTH, DIRECTION_WEST }
+ for _, direction in ipairs(directions) do
+ local position = creature:getPosition()
+ position:getNextPosition(direction)
+ local tile = Tile(position)
+ if tile:isWalkable(false, false, false, true, true) then
+ local item = Game.createItem(puddleId, 1, position)
+ item:decay()
+ break
+ end
+ end
+end
+
+alchemistContainerDeath:register()
+
+local alchemistContainerSpawns = GlobalEvent("fight.the-monster.containers.alchemist.onThink")
+local alchemistContainerPositions = {
+ { x = 33834, y = 32585, z = 12 },
+ { x = 33840, y = 32585, z = 12 },
+ { x = 33845, y = 32587, z = 12 },
+ { x = 33845, y = 32595, z = 12 },
+ { x = 33840, y = 32597, z = 12 },
+ { x = 33834, y = 32597, z = 12 },
+ { x = 33829, y = 32595, z = 12 },
+ { x = 33829, y = 32592, z = 12 },
+ { x = 33829, y = 32590, z = 12 },
+ { x = 33829, y = 32587, z = 12 },
+}
+
+function alchemistContainerSpawns.onThink()
+ for _, position in ipairs(alchemistContainerPositions) do
+ local tile = Tile(position)
+ if tile and tile:getCreatureCount() == 0 then
+ local corpse = tile:getItemById(39949)
+ if corpse then
+ corpse:remove()
+ end
+ local monster = Game.createMonster("alchemist container", position)
+ if monster then
+ monster:registerEvent("fight.the-monster.AlchemistContainerDeath")
+ end
+ end
+ end
+ return true
+end
+
+alchemistContainerSpawns:interval(10000)
+alchemistContainerSpawns:register()
+
+local function getShields(creature)
+ local currentIcon = creature:getIcon("the-monster")
+ if not currentIcon or currentIcon.category ~= CreatureIconCategory_Quests or currentIcon.icon ~= CreatureIconQuests_PurpleShield then
+ return 0
+ end
+ if currentIcon.count <= 0 then
+ creature:removeIcon("magma-bubble")
+ return 0
+ end
+ return currentIcon.count
+end
+
+local function setShields(creature, count)
+ if count <= 0 then
+ creature:removeIcon("the-monster")
+ return
+ end
+ creature:setIcon("the-monster", CreatureIconCategory_Quests, CreatureIconQuests_PurpleShield, count)
+end
+
+local monsterHealth = CreatureEvent("fight.the-monster.TheMonsterHealth")
+function monsterHealth.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType)
+ if not creature then
+ return primaryDamage, primaryType, secondaryDamage, secondaryType
+ end
+ local shields = getShields(creature)
+ local multiplier = 1 - shields * 0.05
+ return primaryDamage * multiplier, primaryType, secondaryDamage * multiplier, secondaryType
+end
+
+monsterHealth:register()
+
+local monsterDeath = CreatureEvent("fight.the-monster.TheMonsterDeath")
+function monsterDeath.onDeath(creature)
+ encounter:nextStage()
+end
+
+monsterDeath:register()
+
+local puddleStepIn = MoveEvent("fight.the-monster.PuddleStepIn")
+function puddleStepIn.onStepIn(creature, item, position, fromPosition)
+ if not creature or creature:getName() ~= "The Monster" then
+ return true
+ end
+ item:remove()
+ local current = getShields(creature)
+ if current <= 0 then
+ return true
+ end
+ setShields(creature, current - 1)
+ creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK)
+ return true
+end
+
+puddleStepIn:type("stepin")
+puddleStepIn:id(puddleId)
+puddleStepIn:register()
diff --git a/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua
new file mode 100644
index 00000000000..71528272d4b
--- /dev/null
+++ b/data-otservbr-global/scripts/quests/cradle_of_monsters/the_monster_lever.lua
@@ -0,0 +1,28 @@
+local config = {
+ boss = { name = "The Monster" },
+ encounter = "The Monster",
+ requiredLevel = 250,
+
+ playerPositions = {
+ { pos = { x = 33812, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT },
+ { pos = { x = 33811, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT },
+ { pos = { x = 33810, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT },
+ { pos = { x = 33809, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT },
+ { pos = { x = 33808, y = 32584, z = 12 }, teleport = { x = 33831, y = 32591, z = 12 }, effect = CONST_ME_TELEPORT },
+ },
+ specPos = {
+ from = { x = 33828, y = 32584, z = 12 },
+ to = { x = 33846, y = 32598, z = 12 },
+ },
+ exitTeleporter = { x = 33829, y = 32591, z = 12 },
+ exit = { x = 33810, y = 32587, z = 12 },
+}
+
+local lever = BossLever(config)
+lever:position({ x = 33813, y = 32584, z = 12 })
+lever:register()
+
+-- Entrance to lever room
+SimpleTeleport({ x = 33792, y = 32581, z = 12 }, { x = 33806, y = 32584, z = 12 })
+-- Exit from lever room
+SimpleTeleport({ x = 33804, y = 32584, z = 12 }, { x = 33792, y = 32579, z = 12 })
diff --git a/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua b/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua
new file mode 100644
index 00000000000..c3eed4ab17d
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/arachir_the_ancient_one.lua
@@ -0,0 +1,23 @@
+local zone = Zone("drefia.arachir")
+zone:addArea(Position(32963, 32399, 12), Position(32965, 32401, 12))
+
+local raid = Raid("drefia.arachir", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Arachir the Ancient One",
+ amount = 1,
+ position = Position(32964, 32400, 12),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua b/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua
new file mode 100644
index 00000000000..a510439bdfc
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/diblis_the_fair.lua
@@ -0,0 +1,23 @@
+local zone = Zone("nargor.diblis")
+zone:addArea(Position(32008, 32794, 10), Position(32010, 32797, 10))
+
+local raid = Raid("nargor.diblis", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Diblis The Fair",
+ amount = 1,
+ position = Position(32009, 32795, 10),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/furyosa.lua b/data-otservbr-global/scripts/raids/bosses/furyosa.lua
new file mode 100644
index 00000000000..083e0b23afa
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/furyosa.lua
@@ -0,0 +1,32 @@
+local zone = Zone("fury-gates.furiosa")
+zone:addArea(Position(33257, 32659, 14), Position(33342, 31867, 15))
+
+local raid = Raid("fury-gates.furiosa", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.01,
+ targetChancePerDay = 0.01,
+ maxChancePerCheck = 0.6,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Demon",
+ amount = 80,
+ },
+ })
+ :autoAdvance("1m")
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Furyosa",
+ amount = 1,
+ position = Position(33281, 31804, 15),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/hirintror.lua b/data-otservbr-global/scripts/raids/bosses/hirintror.lua
new file mode 100644
index 00000000000..a2343d80a37
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/hirintror.lua
@@ -0,0 +1,23 @@
+local zone = Zone("svargrond.hirintror")
+zone:addArea(Position(32100, 31166, 9), Position(32102, 31168, 9))
+
+local raid = Raid("svargrond.hirintror", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Hirintror",
+ amount = 1,
+ position = Position(32101, 31167, 9),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/mawhawk.lua b/data-otservbr-global/scripts/raids/bosses/mawhawk.lua
new file mode 100644
index 00000000000..f71a64eb380
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/mawhawk.lua
@@ -0,0 +1,22 @@
+local zone = Zone("roshamuul.mawhawk")
+zone:addArea(Position(33702, 32460, 7), Position(33704, 32462, 7))
+
+local raid = Raid("roshamuul.mawhawk", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.04,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.4,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Mawhawk",
+ amount = 1,
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua b/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua
new file mode 100644
index 00000000000..ddb86dc7e57
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/sir_valorcrest.lua
@@ -0,0 +1,23 @@
+local zone = Zone("edron.valorcrest")
+zone:addArea(Position(33263, 31767, 10), Position(33265, 31769, 10))
+
+local raid = Raid("edron.valorcrest", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Sir Valorcrest",
+ amount = 1,
+ position = Position(33264, 31768, 10),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua b/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua
new file mode 100644
index 00000000000..ff82c9fca57
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/the_old_widow.lua
@@ -0,0 +1,38 @@
+local zone = Zone("venore.the-old-widow")
+zone:addArea(Position(32292, 32292, 12), Position(32796, 32306, 12))
+
+local raid = Raid("venore.the-old-widow", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid:addBroadcast("The mating season of the giant spiders is at hand. Leave the plains of havoc as fast as you can."):autoAdvance("30s")
+
+raid:addBroadcast("Giant spiders have gathered on the plains of havoc for their mating season. Beware!"):autoAdvance("3m")
+
+for _ = 1, 4 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Giant Spider",
+ amount = 8,
+ },
+ })
+ :autoAdvance("10s")
+end
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "The Old Widow",
+ amount = 1,
+ position = Position(32776, 32296, 7),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua b/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua
new file mode 100644
index 00000000000..268636736bd
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/the_pale_count.lua
@@ -0,0 +1,23 @@
+local zone = Zone("drefia.the-pale-count")
+zone:addArea(Position(32968, 32419, 15), Position(32970, 32421, 15))
+
+local raid = Raid("drefia.the-pale-count", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.01,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.7,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "The Pale Count",
+ amount = 1,
+ position = Position(32969, 32420, 15),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/the_welter.lua b/data-otservbr-global/scripts/raids/bosses/the_welter.lua
new file mode 100644
index 00000000000..3daadde51f8
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/the_welter.lua
@@ -0,0 +1,22 @@
+local zone = Zone("ankrahmun.the-welter")
+zone:addArea(Position(33025, 32659, 5), Position(33027, 32661, 5))
+
+local raid = Raid("ankrahmun.the-welter", {
+ zone = zone,
+ allowedDays = { "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.01,
+ targetChancePerDay = 0.01,
+ maxChancePerCheck = 0.6,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "The Welter",
+ amount = 1,
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/tyrn.lua b/data-otservbr-global/scripts/raids/bosses/tyrn.lua
new file mode 100644
index 00000000000..839f462872d
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/tyrn.lua
@@ -0,0 +1,22 @@
+local zone = Zone("darashia.tyrn")
+zone:addArea(Position(33055, 32392, 14), Position(33057, 32394, 14))
+
+local raid = Raid("darashia.tyrn", {
+ zone = zone,
+ allowedDays = { "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.04,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.4,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Tyrn",
+ amount = 1,
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua b/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua
new file mode 100644
index 00000000000..78ef336a953
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/weakened_shlorg.lua
@@ -0,0 +1,23 @@
+local zone = Zone("edron.weakened-shlorg")
+zone:addArea(Position(33163, 31715, 9), Position(33165, 31717, 9))
+
+local raid = Raid("edron.weakened-shlorg", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Weakened Shlorg",
+ amount = 1,
+ position = Position(33164, 31716, 9),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/bosses/white_pale.lua b/data-otservbr-global/scripts/raids/bosses/white_pale.lua
new file mode 100644
index 00000000000..880660d5c99
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/bosses/white_pale.lua
@@ -0,0 +1,23 @@
+local zone = Zone("edron.white-pale")
+zone:addArea(Position(33263, 31874, 11), Position(33265, 31876, 11))
+
+local raid = Raid("edron.white-pale", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.8,
+})
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "White Pale",
+ amount = 1,
+ position = Position(33264, 31875, 11),
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/draptor.lua b/data-otservbr-global/scripts/raids/monsters/draptor.lua
new file mode 100644
index 00000000000..002e06ce8de
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/draptor.lua
@@ -0,0 +1,47 @@
+local zone = Zone("farmine.draptor")
+zone:addArea(Position(33195, 31160, 7), Position(33286, 31247, 7))
+
+local raid = Raid("farmine.draptor", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.6,
+ minGapBetween = "12h",
+})
+
+raid:addBroadcast("The dragons of the Dragonblaze Mountains have descended to Zao to protect the lizardkin!"):autoAdvance("30s")
+
+for i = 1, 3 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Dragon",
+ amount = 50,
+ },
+ })
+ :autoAdvance("2m")
+end
+
+for i = 1, 8 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Draptor",
+ amount = 1,
+ },
+ })
+ :autoAdvance("10s")
+end
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Grand Mother Foulscale",
+ amount = 1,
+ },
+ })
+ :autoAdvance("10s")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua b/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua
new file mode 100644
index 00000000000..d4bb999f881
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/midnight_panther.lua
@@ -0,0 +1,29 @@
+local zone = Zone("tiquanda.midnight-panther")
+zone:addArea(Position(32847, 32697, 7), Position(32871, 32738, 7))
+
+local raid = Raid("tiquanda.midnight-panther", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 0.03,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.9,
+})
+
+local possiblePositions = {
+ Position(32847, 32697, 7),
+ Position(32871, 32717, 7),
+ Position(32856, 32738, 7),
+}
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Midnight Panther",
+ amount = 1,
+ position = possiblePositions[math.random(1, #possiblePositions)],
+ },
+ })
+ :autoAdvance("24h")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/rats.lua b/data-otservbr-global/scripts/raids/monsters/rats.lua
new file mode 100644
index 00000000000..11d1a440bb2
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/rats.lua
@@ -0,0 +1,42 @@
+local zone = Zone("thais.rats")
+zone:addArea(Position(32331, 32182, 7), Position(32426, 32261, 7))
+
+local raid = Raid("thais.rats", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.04,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.4,
+ minGapBetween = "36h",
+})
+
+raid:addBroadcast("Rat Plague in Thais!"):autoAdvance("5s")
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Rat",
+ amount = 10,
+ },
+ {
+ name = "Cave Rat",
+ amount = 10,
+ },
+ })
+ :autoAdvance("10m")
+
+raid
+ :addSpawnMonsters({
+ {
+ name = "Rat",
+ amount = 20,
+ },
+ {
+ name = "Cave Rat",
+ amount = 20,
+ },
+ })
+ :autoAdvance("10m")
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua b/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua
new file mode 100644
index 00000000000..b86f23b54c3
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/undead_cavebear.lua
@@ -0,0 +1,25 @@
+local zone = Zone("farmine.draptor")
+zone:addArea(Position(31909, 32554, 7), Position(31983, 32579, 7))
+
+local raid = Raid("farmine.draptor", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.6,
+ minGapBetween = "12h",
+})
+
+for i = 1, 3 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Undead Cavebear",
+ amount = 3,
+ },
+ })
+ :autoAdvance("2m")
+end
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/wild_horses.lua b/data-otservbr-global/scripts/raids/monsters/wild_horses.lua
new file mode 100644
index 00000000000..b5e6063684e
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/wild_horses.lua
@@ -0,0 +1,27 @@
+local zone = Zone("thais.wild-horses")
+zone:addArea(Position(32456, 32193, 7), Position(32491, 32261, 7))
+zone:addArea(Position(32431, 32240, 7), Position(32464, 32280, 7))
+
+local raid = Raid("thais.wild-horses", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 3,
+ initialChance = 30,
+ targetChancePerDay = 50,
+ maxChancePerCheck = 50,
+ maxChecksPerDay = 2,
+ minGapBetween = "12h",
+})
+
+for _ = 1, 7 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Wild Horse",
+ amount = 3,
+ },
+ })
+ :autoAdvance("3h")
+end
+
+raid:register()
diff --git a/data-otservbr-global/scripts/raids/monsters/yeti.lua b/data-otservbr-global/scripts/raids/monsters/yeti.lua
new file mode 100644
index 00000000000..0161b7eacd2
--- /dev/null
+++ b/data-otservbr-global/scripts/raids/monsters/yeti.lua
@@ -0,0 +1,29 @@
+local zone = Zone("folda.yeti")
+zone:addArea(Position(31991, 31580, 7), Position(32044, 31616, 7))
+
+local raid = Raid("folda.yeti", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 2,
+ initialChance = 0.02,
+ targetChancePerDay = 0.02,
+ maxChancePerCheck = 0.6,
+ minGapBetween = "48h",
+})
+
+raid:addBroadcast("Something is moving to the icy grounds of Folda."):autoAdvance("30s")
+raid:addBroadcast("Many Yetis are emerging from the icy mountains of Folda."):autoAdvance("30s")
+raid:addBroadcast("Numerous Yetis are dominating Folda, beware!"):autoAdvance("60s")
+
+for i = 1, 20 do
+ raid
+ :addSpawnMonsters({
+ {
+ name = "Yeti",
+ amount = 3,
+ },
+ })
+ :autoAdvance("3m")
+end
+
+raid:register()
diff --git a/data-otservbr-global/scripts/spells/monster/death_barrage.lua b/data-otservbr-global/scripts/spells/monster/death_barrage.lua
new file mode 100644
index 00000000000..2657bcc12e3
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/death_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SUDDENDEATH)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castDeathMissile, 150, creature:getId(), var)
+ addEvent(castDeathMissile, 300, creature:getId(), var)
+ addEvent(castDeathMissile, 450, creature:getId(), var)
+ return
+end
+
+function castDeathMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("death barrage")
+spell:words("###6042")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua b/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua
new file mode 100644
index 00000000000..662f2fd108c
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/destroy_magic_walls.lua
@@ -0,0 +1,33 @@
+local magicWallIds = {
+ ITEM_MAGICWALL_SAFE,
+ ITEM_MAGICWALL,
+ ITEM_WILDGROWTH_SAFE,
+ ITEM_WILDGROWTH,
+}
+
+local spell = Spell("instant")
+function spell.onCastSpell(creature, var)
+ -- check tiles around the caster
+ local position = creature:getPosition()
+ for x = -2, 2 do
+ for y = -2, 2 do
+ local tile = Tile(position.x + x, position.y + y, position.z)
+ if tile then
+ local item = tile:getTopVisibleThing()
+ if item and table.contains(magicWallIds, item:getId()) then
+ item:remove()
+ position:sendMagicEffect(CONST_ME_POFF)
+ return true -- only one magic wall per cast
+ end
+ end
+ end
+ end
+ return true
+end
+
+spell:name("destroy magic walls")
+spell:words("###6045")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua b/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua
new file mode 100644
index 00000000000..59948c3f0c2
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/diabolic_imp_fireball.lua
@@ -0,0 +1,26 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREATTACK)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE)
+arr_small = {
+ { 0, 1, 0 },
+ { 1, 3, 1 },
+ { 0, 1, 0 },
+}
+
+local area = createCombatArea(arr_small)
+combat:setArea(area)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ return combat:execute(creature, var)
+end
+
+spell:name("diabolic imp fireball")
+spell:words("###6035")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua
new file mode 100644
index 00000000000..f616e8b8cee
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/doctor_marrow_explosion.lua
@@ -0,0 +1,90 @@
+local spellCombat = Combat()
+spellCombat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE)
+spellCombat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ORANGETELEPORT)
+
+spellCombat:setArea(createCombatArea(AREA_CIRCLE6X6))
+
+local damage = 0
+local crit = false
+
+local function paralyze(player)
+ if not player then
+ return true
+ end
+
+ local condition = Condition(CONDITION_PARALYZE)
+ condition:setParameter(CONDITION_PARAM_TICKS, 500)
+ condition:setFormula(-0.94, 0, -0.97, 0)
+ player:addCondition(condition)
+ return true
+end
+
+local spell = Spell("instant")
+function onTargetCreature(creature, target)
+ if not targetPos then
+ return true
+ end
+ local master = target:getMaster()
+ if not target:isPlayer() and not (master or master:isPlayer()) then
+ return true
+ end
+
+ local distance = math.floor(targetPos:getDistance(target:getPosition()))
+ local actualDamage = damage / (2 ^ distance)
+ doTargetCombatHealth(0, target, COMBAT_EARTHDAMAGE, actualDamage, actualDamage, CONST_ME_NONE)
+ if crit then
+ target:getPosition():sendMagicEffect(CONST_ME_CRITICAL_DAMAGE)
+ end
+ return true
+end
+
+spellCombat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature")
+
+function spell.onCastSpell(creature, var)
+ local target = Creature(var:getNumber())
+ if not target then
+ return false
+ end
+ local targetPos = target:getPosition()
+ target:say("You are being targeted by Doctor Marrow's explosion!", TALKTYPE_MONSTER_SAY, false, target)
+ creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK)
+ targetPos:sendMagicEffect(CONST_ME_ORANGETELEPORT)
+
+ local totalDelay = 4000
+
+ for i = 0, totalDelay / 100 do
+ addEvent(function(pos)
+ local spectators = Game.getSpectators(pos, false, true, 6, 6, 6, 6)
+ for _, spectator in ipairs(spectators) do
+ if spectator:isPlayer() then
+ paralyze(spectator)
+ end
+ end
+ end, i * 100, targetPos)
+ end
+
+ addEvent(function(cid)
+ local creature = Creature(cid)
+ creature:getPosition():sendMagicEffect(CONST_ME_ORANGE_ENERGY_SPARK)
+ targetPos:sendMagicEffect(CONST_ME_ORANGETELEPORT)
+ end, 2000, creature:getId())
+
+ addEvent(function(cid, pos)
+ damage = -math.random(3500, 7000)
+ if math.random(1, 100) <= 10 then
+ crit = true
+ damage = damage * 1.5
+ else
+ crit = false
+ end
+ spellCombat:execute(creature, Variant(pos))
+ end, totalDelay, creature:getId(), targetPos)
+ return true
+end
+
+spell:name("doctor marrow explosion")
+spell:words("###6044")
+spell:needLearn(true)
+spell:needTarget(true)
+spell:cooldown(10000)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/earth_barrage.lua b/data-otservbr-global/scripts/spells/monster/earth_barrage.lua
new file mode 100644
index 00000000000..727db77f8ca
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/earth_barrage.lua
@@ -0,0 +1,29 @@
+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_SMALLEARTH)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castEarthMissile, 150, creature:getId(), var)
+ addEvent(castEarthMissile, 300, creature:getId(), var)
+ addEvent(castEarthMissile, 450, creature:getId(), var)
+ return
+end
+
+function castEarthMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("earth barrage")
+spell:words("###6039")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/energy_barrage.lua b/data-otservbr-global/scripts/spells/monster/energy_barrage.lua
new file mode 100644
index 00000000000..1b36296fa35
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/energy_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_PURPLEENERGY)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_ENERGY)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castEnergyMissile, 150, creature:getId(), var)
+ addEvent(castEnergyMissile, 300, creature:getId(), var)
+ addEvent(castEnergyMissile, 450, creature:getId(), var)
+ return
+end
+
+function castEnergyMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("energy barrage")
+spell:words("###6038")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/exploding_cask.lua b/data-otservbr-global/scripts/spells/monster/exploding_cask.lua
new file mode 100644
index 00000000000..5448d0733ff
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/exploding_cask.lua
@@ -0,0 +1,106 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
+combat:setArea(createCombatArea(AREA_CIRCLE2X2))
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA)
+
+local combatCast = Combat()
+
+local barrelId = 23485
+local bombArea = {
+ { 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 },
+}
+
+function onTargetCreature(creature, target)
+ local min = -800
+ local max = -1100
+
+ if target:isPlayer() or (target:getMaster() and target:getMaster():isPlayer()) then
+ doTargetCombatHealth(0, target, COMBAT_FIREDAMAGE, min, max, CONST_ME_NONE)
+ end
+
+ return true
+end
+
+combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature")
+
+local function createBarrel()
+ local template = Game.createItem(barrelId, 1)
+
+ template:setDuration(3)
+ template:stopDecay()
+
+ return template
+end
+
+createBarrel()
+
+local function explodeBomb(position, creatureId)
+ local var = {}
+
+ var.instantName = "Cask Explode"
+ var.runeName = ""
+ var.type = 2 -- VARIANT_POSITION
+ var.pos = position
+
+ combat:execute(Creature(creatureId), var)
+end
+
+local function bombTimer(seconds, pos)
+ local spectators = Game.getSpectators(pos, false, true, 11, 11, 9, 9)
+
+ if #spectators > 0 then
+ for i = 1, #spectators do
+ spectators[i]:say(seconds, TALKTYPE_MONSTER_SAY, false, spectators[i], pos)
+ end
+ end
+end
+
+function onTargetCreature(creature, target)
+ if not creature or not target then
+ return false
+ end
+
+ local position = target:getPosition()
+ local template = createBarrel()
+ template:setOwner(creature:getId())
+
+ local tile = Tile(position)
+ local item = template:clone()
+ tile:addItemEx(item)
+ item:setDuration(3)
+ item:decay()
+ item:setActionId(IMMOVABLE_ACTION_ID)
+
+ addEvent(explodeBomb, 3000, position, creature:getId())
+ bombTimer(3, position)
+ addEvent(bombTimer, 1000, 2, position, creature:getId())
+ addEvent(bombTimer, 2000, 1, position, creature:getId())
+
+ return true
+end
+
+combatCast:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature")
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ if not creature or not var then
+ return false
+ end
+
+ var.instantName = "Exploding Cask Cast"
+ return combatCast:execute(creature, var)
+end
+
+spell:name("exploding cask")
+spell:words("###6049")
+spell:range(6)
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/fire_barrage.lua b/data-otservbr-global/scripts/spells/monster/fire_barrage.lua
new file mode 100644
index 00000000000..5fefadee483
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/fire_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREATTACK)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_FIRE)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castFireMissile, 150, creature:getId(), var)
+ addEvent(castFireMissile, 300, creature:getId(), var)
+ addEvent(castFireMissile, 450, creature:getId(), var)
+ return
+end
+
+function castFireMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("fire barrage")
+spell:words("###6037")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua b/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua
new file mode 100644
index 00000000000..98e14ec7985
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/fire_wave_delayed.lua
@@ -0,0 +1,144 @@
+local combatWarn = Combat()
+combatWarn:setParameter(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE)
+combatWarn:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED)
+
+local combat1, combat2, combat3, combat4, combat5, combat6 = Combat(), Combat(), Combat(), Combat(), Combat(), Combat()
+
+local combats = { combat1, combat2, combat3, combat4, combat5, combat6 }
+
+for _, combat in pairs(combats) do
+ combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
+ combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA)
+ combat:setFormula(COMBAT_FORMULA_DAMAGE, 0, 700, 1500, 0)
+end
+
+arr = {
+ { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
+ { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr1 = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr2 = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr3 = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr4 = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr5 = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+arr6 = {
+ { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+local area = createCombatArea(arr)
+combatWarn:setArea(area)
+combat1:setArea(createCombatArea(arr1))
+combat2:setArea(createCombatArea(arr2))
+combat3:setArea(createCombatArea(arr3))
+combat4:setArea(createCombatArea(arr4))
+combat5:setArea(createCombatArea(arr5))
+combat6:setArea(createCombatArea(arr6))
+
+local spell = Spell("instant")
+
+local function eventRemoveFreeze(creatureid)
+ local creature = Creature(creatureid)
+ if not creature then
+ return
+ end
+
+ if creature:isMoveLocked() then
+ creature:setMoveLocked(false)
+ end
+
+ if creature:isDirectionLocked() then
+ creature:setDirectionLocked(false)
+ end
+end
+
+local function doCombat(combat, creatureId, var)
+ local creature = Creature(creatureId)
+ if not creature then
+ return false
+ end
+
+ combat:execute(creature, var)
+end
+
+function spell.onCastSpell(creature, var)
+ creature:setMoveLocked(true)
+ creature:setDirectionLocked(true)
+
+ combatWarn:execute(creature, var)
+ addEvent(doCombat, 1000, combatWarn, creature:getId(), var)
+ addEvent(doCombat, 2000, combatWarn, creature:getId(), var)
+ addEvent(doCombat, 3000, combat1, creature:getId(), var)
+ addEvent(doCombat, 3100, combat2, creature:getId(), var)
+ addEvent(doCombat, 3200, combat3, creature:getId(), var)
+ addEvent(doCombat, 3300, combat4, creature:getId(), var)
+ addEvent(doCombat, 3400, combat5, creature:getId(), var)
+ addEvent(doCombat, 3500, combat6, creature:getId(), var)
+
+ addEvent(eventRemoveFreeze, 3800, creature:getId())
+ return true
+end
+
+spell:name("fire wave delayed")
+spell:words("###6050")
+spell:isAggressive(true)
+spell:blockWalls(true)
+spell:needLearn(true)
+spell:needDirection(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua b/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua
new file mode 100644
index 00000000000..24db5e641cb
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/half_circle_wave_earth.lua
@@ -0,0 +1,30 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYPOISON)
+
+arr = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 3, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+local area = createCombatArea(arr)
+combat:setArea(area)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ return combat:execute(creature, var)
+end
+
+spell:name("half circle wave earth")
+spell:words("###6047")
+spell:isAggressive(true)
+spell:blockWalls(true)
+spell:needLearn(true)
+spell:needDirection(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua b/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua
new file mode 100644
index 00000000000..1e9547177d8
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/heal_brain_head.lua
@@ -0,0 +1,27 @@
+local spell = Spell("instant")
+local brainHeadPosition = Position(31954, 32325, 10)
+
+function spell.onCastSpell(creature, var)
+ local tile = Tile(brainHeadPosition)
+ local origin = creature:getPosition()
+ if not tile then
+ return false
+ end
+
+ origin:sendDistanceEffect(brainHeadPosition, CONST_ANI_HOLY)
+ brainHeadPosition:sendMagicEffect(CONST_ME_MAGIC_BLUE)
+ if tile:getTopCreature() and tile:getTopCreature():isMonster() then
+ if tile:getTopCreature():getName():lower() == "brain head" then
+ tile:getTopCreature():addHealth(math.random(300, 500))
+ end
+ end
+ return true
+end
+
+spell:name("heal brain head")
+spell:words("###6051")
+spell:isAggressive(true)
+spell:blockWalls(true)
+spell:needTarget(false)
+spell:needLearn(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/holy_barrage.lua b/data-otservbr-global/scripts/spells/monster/holy_barrage.lua
new file mode 100644
index 00000000000..2ca5761dd8d
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/holy_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLHOLY)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castHolyMissile, 150, creature:getId(), var)
+ addEvent(castHolyMissile, 300, creature:getId(), var)
+ addEvent(castHolyMissile, 450, creature:getId(), var)
+ return
+end
+
+function castHolyMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("holy barrage")
+spell:words("###6041")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/ice_barrage.lua b/data-otservbr-global/scripts/spells/monster/ice_barrage.lua
new file mode 100644
index 00000000000..dc4b9500c14
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/ice_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_SMALLICE)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castIceMissile, 150, creature:getId(), var)
+ addEvent(castIceMissile, 300, creature:getId(), var)
+ addEvent(castIceMissile, 450, creature:getId(), var)
+ return
+end
+
+function castIceMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("ice barrage")
+spell:words("###6040")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/mort_ring.lua b/data-otservbr-global/scripts/spells/monster/mort_ring.lua
new file mode 100644
index 00000000000..c65c2b63c5d
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/mort_ring.lua
@@ -0,0 +1,17 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MORTAREA)
+combat:setArea(createCombatArea(AREA_RING1_BURST3))
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ return combat:execute(creature, var)
+end
+
+spell:name("mort ring")
+spell:words("###6036")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/physical_barrage.lua b/data-otservbr-global/scripts/spells/monster/physical_barrage.lua
new file mode 100644
index 00000000000..68b9c629887
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/physical_barrage.lua
@@ -0,0 +1,29 @@
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_DRAWBLOOD)
+combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_THROWINGSTAR)
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ combat:execute(creature, var)
+ addEvent(castPhysicalMissile, 150, creature:getId(), var)
+ addEvent(castPhysicalMissile, 300, creature:getId(), var)
+ addEvent(castPhysicalMissile, 450, creature:getId(), var)
+ return
+end
+
+function castPhysicalMissile(cid, var)
+ local creature = Creature(cid)
+ if creature and var then
+ combat:execute(creature, var)
+ end
+end
+
+spell:name("physical barrage")
+spell:words("###6043")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/teleport_strike.lua b/data-otservbr-global/scripts/spells/monster/teleport_strike.lua
new file mode 100644
index 00000000000..5e073675e61
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/teleport_strike.lua
@@ -0,0 +1,64 @@
+local spell = Spell("instant")
+
+local smokeArray = {
+ { 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 0, 0, 0 },
+ { 0, 0, 1, 3, 1, 0, 0 },
+ { 0, 0, 0, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0 },
+}
+
+local smokeCombat = Combat()
+smokeCombat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_GREENSMOKE)
+smokeCombat:setArea(createCombatArea(smokeArray))
+
+function spell.onCastSpell(creature, var)
+ local pos = creature:getPosition()
+
+ local target = Creature(var:getNumber())
+ local tarPos = target:getPosition()
+ local direction = target:getDirection()
+ local pos1
+ local pos2
+ local pos3
+ if direction == 0 then
+ pos1 = Position(tarPos.x, tarPos.y + 1, tarPos.z)
+ pos2 = Position(tarPos.x + 1, tarPos.y + 1, tarPos.z)
+ pos3 = Position(tarPos.x - 1, tarPos.y + 1, tarPos.z)
+ elseif direction == 1 then
+ pos1 = Position(tarPos.x - 1, tarPos.y, tarPos.z)
+ pos2 = Position(tarPos.x - 1, tarPos.y + 1, tarPos.z)
+ pos3 = Position(tarPos.x - 1, tarPos.y - 1, tarPos.z)
+ elseif direction == 2 then
+ pos1 = Position(tarPos.x, tarPos.y - 1, tarPos.z)
+ pos2 = Position(tarPos.x - 1, tarPos.y - 1, tarPos.z)
+ pos3 = Position(tarPos.x + 1, tarPos.y - 1, tarPos.z)
+ elseif direction == 3 then
+ pos1 = Position(tarPos.x + 1, tarPos.y, tarPos.z)
+ pos2 = Position(tarPos.x + 1, tarPos.y - 1, tarPos.z)
+ pos3 = Position(tarPos.x + 1, tarPos.y + 1, tarPos.z)
+ end
+
+ if Tile(pos1) and Tile(pos1):isWalkable(true, true, true, true) then
+ smokeCombat:execute(creature, Variant(pos))
+ creature:teleportTo(pos1)
+ elseif Tile(pos2) and Tile(pos2):isWalkable(true, true, true, true) then
+ smokeCombat:execute(creature, Variant(pos))
+ creature:teleportTo(pos2)
+ elseif Tile(pos3) and Tile(pos3):isWalkable(true, true, true, true) then
+ smokeCombat:execute(creature, Variant(pos))
+ creature:teleportTo(pos3)
+ end
+
+ return
+end
+
+spell:name("teleport strike")
+spell:words("###6048")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(false)
+spell:needTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua b/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua
new file mode 100644
index 00000000000..6bcf412cad2
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/werecrocodile_fire_ring.lua
@@ -0,0 +1,33 @@
+local arrLarge = {
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
+ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
+ { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+}
+
+local combat = Combat()
+combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE)
+combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HITBYFIRE)
+combat:setArea(createCombatArea(arrLarge))
+
+local spell = Spell("instant")
+
+function spell.onCastSpell(creature, var)
+ return combat:execute(creature, var)
+end
+
+spell:name("werecrocodile fire ring")
+spell:words("###6052")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua b/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua
new file mode 100644
index 00000000000..d027c32c11f
--- /dev/null
+++ b/data-otservbr-global/scripts/spells/monster/white_weretiger_ice_ring.lua
@@ -0,0 +1,78 @@
+local arrLarge = {
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
+ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 },
+ { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1 },
+ { 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+}
+
+local arrMedium = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 },
+ { 0, 0, 1, 1, 0, 0, 3, 0, 0, 1, 1, 0, 0 },
+ { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+local arrSmall = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 0, 3, 0, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+}
+
+local combatSmallRing = Combat()
+combatSmallRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
+combatSmallRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA)
+combatSmallRing:setArea(createCombatArea(arrSmall))
+
+local combatMediumRing = Combat()
+combatMediumRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
+combatMediumRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA)
+combatMediumRing:setArea(createCombatArea(arrMedium))
+
+local combatLargeRing = Combat()
+combatLargeRing:setParameter(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE)
+combatLargeRing:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ICEAREA)
+combatLargeRing:setArea(createCombatArea(arrLarge))
+
+local spell = Spell("instant")
+
+local combats = { combatSmallRing, combatMediumRing, combatLargeRing }
+
+function spell.onCastSpell(creature, var)
+ local randomCombat = combats[math.random(#combats)]
+ return randomCombat:execute(creature, var)
+end
+
+spell:name("white weretiger ice ring")
+spell:words("###6053")
+spell:needLearn(true)
+spell:cooldown("2000")
+spell:isSelfTarget(true)
+spell:register()
diff --git a/data-otservbr-global/scripts/spells/support/sharpshooter.lua b/data-otservbr-global/scripts/spells/support/sharpshooter.lua
index 1003128a6c4..568fec86e00 100644
--- a/data-otservbr-global/scripts/spells/support/sharpshooter.lua
+++ b/data-otservbr-global/scripts/spells/support/sharpshooter.lua
@@ -4,9 +4,9 @@ local combat = Combat()
combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_GREEN)
combat:setParameter(COMBAT_PARAM_AGGRESSIVE, false)
-local speed = Condition(CONDITION_PARALYZE)
+local speed = Condition(CONDITION_HASTE)
speed:setParameter(CONDITION_PARAM_TICKS, spellDuration)
-speed:setFormula(-0.7, 56, -0.7, 56)
+speed:setFormula(0.7, 0, 0.7, 0)
combat:addCondition(speed)
local exhaustHealGroup = Condition(CONDITION_SPELLGROUPCOOLDOWN)
diff --git a/data-otservbr-global/scripts/spells/support/swift_foot.lua b/data-otservbr-global/scripts/spells/support/swift_foot.lua
index 14e3e5b85f4..5984e2cf250 100644
--- a/data-otservbr-global/scripts/spells/support/swift_foot.lua
+++ b/data-otservbr-global/scripts/spells/support/swift_foot.lua
@@ -6,7 +6,7 @@ combat:setParameter(COMBAT_PARAM_AGGRESSIVE, 0)
local condition = Condition(CONDITION_HASTE)
condition:setParameter(CONDITION_PARAM_TICKS, spellDuration)
-condition:setFormula(0.8, -72, 0.8, -72)
+condition:setFormula(1.8, 72, 1.8, 72)
combat:addCondition(condition)
local spell = Spell("instant")
diff --git a/data-otservbr-global/world/otservbr-zones.xml b/data-otservbr-global/world/otservbr-zones.xml
index a9224bd3c2d..4740d50385c 100644
--- a/data-otservbr-global/world/otservbr-zones.xml
+++ b/data-otservbr-global/world/otservbr-zones.xml
@@ -1,2 +1,4 @@
-
+
+
+
diff --git a/data/items/items.xml b/data/items/items.xml
index ade8054ec07..bce4281b538 100644
--- a/data/items/items.xml
+++ b/data/items/items.xml
@@ -9213,15 +9213,18 @@
+
-
+
-
+
-
@@ -27955,6 +27958,7 @@
-
+
@@ -31349,6 +31353,7 @@
-
+
@@ -41612,6 +41617,7 @@
-
+
-
@@ -43700,11 +43706,13 @@
+
-
+
-
@@ -43750,21 +43758,25 @@
+
-
+
-
+
-
+
-
@@ -44352,24 +44364,28 @@
+
-
+
-
+
-
+
@@ -44579,6 +44595,7 @@
-
+
@@ -49175,6 +49192,7 @@
-
+
-
diff --git a/data/libs/daily_reward/daily_reward.lua b/data/libs/daily_reward/daily_reward.lua
index e3ca90caf27..232d4826021 100644
--- a/data/libs/daily_reward/daily_reward.lua
+++ b/data/libs/daily_reward/daily_reward.lua
@@ -38,19 +38,17 @@ end
function RegenSoul(id, delay)
local soulEvent = DailyRewardBonus.Soul[id]
- local maxsoul = 0
local player = Player(id)
if not player then
stopEvent(soulEvent)
DailyRewardBonus.Soul[id] = nil
return false
end
+ local maxsoul = 100
+ if (configManager.getBoolean(configKeys.VIP_SYSTEM_ENABLED) and player:isVip()) or player:isPremium() then
+ maxsoul = 200
+ end
if player:getTile():hasFlag(TILESTATE_PROTECTIONZONE) then
- if player:isPremium() then
- maxsoul = 200
- else
- maxsoul = 100
- end
if player:getSoul() < maxsoul then
player:addSoul(1)
player:sendTextMessage(MESSAGE_FAILURE, "One soul point has been restored.")
diff --git a/data/libs/encounters_lib.lua b/data/libs/encounters_lib.lua
index 6258f399580..e84bd9a8aed 100644
--- a/data/libs/encounters_lib.lua
+++ b/data/libs/encounters_lib.lua
@@ -17,19 +17,48 @@ setmetatable(EncounterStage, {
}, { __index = EncounterStage })
end,
})
+---@type Delay number|string The delay time to advance to the next stage
+
+---@type AutoAdvanceConfig
+---@field delay Delay
+---@field monstersKilled boolean
---Automatically advances to the next stage after the given delay
----@param delay number|string The delay time to advance to the next stage
-function EncounterStage:autoAdvance(delay)
+---@param config AutoAdvanceConfig|Delay The configuration for the auto advance
+function EncounterStage:autoAdvance(config)
+ if type(config) == "number" or type(config) == "string" then
+ config = { delay = config }
+ end
+ config = config or {}
+
local originalStart = self.start
+ local delay = config.delay
+ local delayElapsed = false
function self.start()
delay = delay or 50 -- 50ms is minimum delay; used here for close to instant advance
- originalStart()
+ if originalStart then
+ originalStart()
+ end
self.encounter:debug("Encounter[{}]:autoAdvance | next stage in: {}", self.encounter.name, delay == 50 and "instant" or delay)
self.encounter:addEvent(function()
- self.encounter:nextStage()
+ delayElapsed = true
+ if not config.monstersKilled then
+ self.encounter:nextStage()
+ end
end, delay)
end
+
+ if config.monstersKilled then
+ local originalTick = self.tick
+ function self.tick()
+ if originaTick then
+ originalTick()
+ end
+ if delayElapsed and self.encounter:countMonsters() == 0 then
+ self.encounter:nextStage()
+ end
+ end
+ end
end
---@class Encounter
@@ -76,8 +105,8 @@ setmetatable(Encounter, {
---Resets the encounter configuration
---@param config EncounterConfig The new configuration
function Encounter:resetConfig(config)
- self.zone = config.zone
- self.spawnZone = config.spawnZone or config.zone
+ self.zone = config.zone:getName()
+ self.spawnZone = config.spawnZone and config.spawnZone:getName() or config.zone:getName()
self.stages = {}
self.currentStage = Encounter.unstarted
self.registered = false
@@ -145,7 +174,7 @@ function Encounter:enterStage(stageNumber, abort)
return true
end
----@alias SpawnMonsterConfig { name: string, amount: number, event: string?, timeLimit: number?, position: Position|table?, positions: Position|table[]?, spawn: function? }
+---@alias SpawnMonsterConfig { name: string|string[], amount: number, event: string?, timeLimit: number?, position: Position|table?, positions: Position|table[]?, spawn: function? }
---Spawns monsters based on the given configuration
---@param config SpawnMonsterConfig The configuration for spawning monsters
@@ -164,15 +193,21 @@ function Encounter:spawnMonsters(config)
if config.position then
table.insert(positions, config.position)
else
- table.insert(positions, self.spawnZone:randomPosition())
+ table.insert(positions, self:getSpawnZone():randomPosition())
end
end
end
for _, position in ipairs(positions) do
- for i = 1, self.timeToSpawnMonsters / 1000 do
- self:addEvent(function(position)
- position:sendMagicEffect(CONST_ME_TELEPORT)
- end, i * 1000, position)
+ if self.timeToSpawnMonsters >= 1000 then
+ for i = 1, self.timeToSpawnMonsters / 1000 do
+ self:addEvent(function(position)
+ position:sendMagicEffect(CONST_ME_TELEPORT)
+ end, i * 1000, position)
+ end
+ end
+ local name = config.name
+ if type(name) == "table" then
+ name = name[math.random(#name)]
end
self:addEvent(function(name, position, event, spawn, timeLimit)
local monster = Game.createMonster(name, position)
@@ -199,10 +234,18 @@ function Encounter:spawnMonsters(config)
monster:remove()
end, config.timeLimit, monster:getID())
end
- end, self.timeToSpawnMonsters, config.name, position, config.event, config.spawn, config.timeLimit)
+ end, self.timeToSpawnMonsters, name, position, config.event, config.spawn, config.timeLimit)
end
end
+function Encounter:getZone()
+ return Zone(self.zone)
+end
+
+function Encounter:getSpawnZone()
+ return Zone(self.spawnZone)
+end
+
---Broadcasts a message to all players
function Encounter:broadcast(...)
if self.global then
@@ -211,25 +254,30 @@ function Encounter:broadcast(...)
end
return
end
- self.zone:sendTextMessage(...)
+ self:getZone():sendTextMessage(...)
end
---Counts the number of monsters with the given name in the encounter zone
---@param name string The name of the monster to count
---@return number The number of monsters with the given name
function Encounter:countMonsters(name)
- return self.zone:countMonsters(name)
+ return self:getZone():countMonsters(name)
end
---Counts the number of players in the encounter zone
---@return number The number of players in the encounter zone
function Encounter:countPlayers()
- return self.zone:countPlayers(IgnoredByMonsters)
+ return self:getZone():countPlayers(IgnoredByMonsters)
end
---Removes all monsters from the encounter zone
function Encounter:removeMonsters()
- self.zone:removeMonsters()
+ self:getZone():removeMonsters()
+end
+
+---Removes all players from the encounter zone
+function Encounter:removePlayers()
+ self:getZone():removePlayers()
end
---Resets the encounter to its initial state
@@ -249,7 +297,7 @@ end
---@param position Position The position to check
---@return boolean True if the position is inside the encounter zone, false otherwise
function Encounter:isInZone(position)
- return self.zone:isInZone(position)
+ return self:getZone():isInZone(position)
end
---Enters the previous stage in the encounter
@@ -341,9 +389,19 @@ function Encounter:addRemoveMonsters()
})
end
+---Adds a stage that removes all players from the encounter zone
+---@return boolean True if the remove monsters stage is added successfully, false otherwise
+function Encounter:addRemovePlayers()
+ return self:addStage({
+ start = function()
+ self:removePlayers()
+ end,
+ })
+end
+
---Automatically starts the encounter when players enter the zone
function Encounter:startOnEnter()
- local zoneEvents = ZoneEvent(self.zone)
+ local zoneEvents = ZoneEvent(self:getZone())
function zoneEvents.afterEnter(zone, creature)
if not self.registered then
diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua
index 90e739792cc..ce9c8177d9d 100644
--- a/data/libs/functions/player.lua
+++ b/data/libs/functions/player.lua
@@ -554,26 +554,34 @@ function Player.updateHazard(self)
return true
end
-function Player:addItemStoreInbox(itemId, amount, moveable)
+function Player:addItemStoreInboxEx(item, moveable, setOwner)
local inbox = self:getSlotItem(CONST_SLOT_STORE_INBOX)
if not moveable then
- for _, item in pairs(inbox:getItems()) do
- if item:getId() == itemId then
- item:removeAttribute(ITEM_ATTRIBUTE_STORE)
- end
- end
+ item:setOwner(self)
+ item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
+ elseif setOwner then
+ item:setOwner(self)
end
+ inbox:addItemEx(item, INDEX_WHEREEVER, FLAG_NOLIMIT)
+ return item
+end
- local newItem = inbox:addItem(itemId, amount, INDEX_WHEREEVER, FLAG_NOLIMIT)
-
- if not moveable then
- for _, item in pairs(inbox:getItems()) do
- if item:getId() == itemId then
- item:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
- end
+function Player:addItemStoreInbox(itemId, amount, moveable, setOwner)
+ local iType = ItemType(itemId)
+ if not iType then
+ return nil
+ end
+ if iType:isStackable() then
+ while amount > iType:getStackSize() do
+ self:addItemStoreInboxEx(Game.createItem(itemId, iType:getStackSize()), moveable, setOwner)
+ amount = amount - iType:getStackSize()
end
end
- return newItem
+ local item = Game.createItem(itemId, amount)
+ if not item then
+ return nil
+ end
+ return self:addItemStoreInboxEx(item, moveable, setOwner)
end
---@param monster Monster
diff --git a/data/libs/hazard_lib.lua b/data/libs/hazard_lib.lua
index c13f120f740..f9338299e79 100644
--- a/data/libs/hazard_lib.lua
+++ b/data/libs/hazard_lib.lua
@@ -8,12 +8,14 @@ function Hazard.new(prototype)
instance.name = prototype.name
instance.from = prototype.from
instance.to = prototype.to
+ instance.minLevel = prototype.minLevel or 1
instance.maxLevel = prototype.maxLevel
instance.storageMax = prototype.storageMax ---@deprecated
instance.storageCurrent = prototype.storageCurrent ---@deprecated
instance.crit = prototype.crit
instance.dodge = prototype.dodge
instance.damageBoost = prototype.damageBoost
+ instance.defenseBoost = prototype.defenseBoost
instance.zone = Zone(instance.name)
if instance.from and instance.to then
@@ -41,19 +43,43 @@ function Hazard:getHazardPlayerAndPoints(damageMap)
end
if hazardPoints == -1 then
- hazardPoints = 1
+ hazardPoints = self.minLevel
end
return hazardPlayer, hazardPoints
end
+function Hazard:getCurrentLevel(players)
+ local hazardPlayer = nil
+ local hazardPoints = -1
+ for _, player in ipairs(players) do
+ local playerHazardPoints = self:getPlayerCurrentLevel(player)
+
+ if playerHazardPoints < hazardPoints or hazardPoints == -1 then
+ hazardPlayer = player
+ hazardPoints = playerHazardPoints
+ end
+ end
+
+ if hazardPoints == -1 then
+ hazardPoints = self.minLevel
+ end
+
+ return hazardPoints
+end
+
function Hazard:getPlayerCurrentLevel(player)
if self.storageCurrent then
local fromStorage = player:getStorageValue(self.storageCurrent)
- return fromStorage <= 0 and 1 or fromStorage
+ return fromStorage <= 0 and self.minLevel or fromStorage
end
- local fromKV = player:kv():scoped(self.name):get("currentLevel") or 1
- return fromKV <= 0 and 1 or fromKV
+ local fromKV = player:kv():scoped(self.name):get("current-level") or self.minLevel
+ return fromKV <= 0 and self.minLevel or fromKV
+end
+
+function Hazard:getPlayerMaxLevelEver(player)
+ local fromKV = player:kv():scoped(self.name):get("max-level-set") or self.minLevel
+ return fromKV <= 0 and self.minLevel or fromKV
end
function Hazard:setPlayerCurrentLevel(player, level)
@@ -64,7 +90,11 @@ function Hazard:setPlayerCurrentLevel(player, level)
if self.storageCurrent then
player:setStorageValue(self.storageCurrent, level)
else
- player:kv():scoped(self.name):set("currentLevel", level)
+ player:kv():scoped(self.name):set("current-level", level)
+ local maxEver = player:kv():scoped(self.name):get("max-level-set") or self.minLevel
+ if level > maxEver then
+ player:kv():scoped(self.name):set("max-level-set", level)
+ end
end
local zones = player:getZones()
if not zones then
@@ -86,11 +116,10 @@ end
function Hazard:getPlayerMaxLevel(player)
if self.storageMax then
local fromStorage = player:getStorageValue(self.storageMax)
- return fromStorage <= 0 and 1 or fromStorage
+ return fromStorage <= 0 and self.minLevel or fromStorage
end
- local fromKV = player:kv():scoped(self.name):get("maxLevel") or 1
-
- return fromKV <= 0 and 1 or fromKV
+ local fromKV = player:kv():scoped(self.name):get("max-level") or self.minLevel
+ return fromKV <= 0 and self.minLevel or fromKV
end
function Hazard:levelUp(player)
@@ -110,7 +139,7 @@ function Hazard:setPlayerMaxLevel(player, level)
player:setStorageValue(self.storageMax, level)
return
end
- player:kv():scoped(self.name):set("maxLevel", level)
+ player:kv():scoped(self.name):set("max-level", level)
end
function Hazard:isInZone(position)
@@ -181,6 +210,7 @@ function HazardMonster.onSpawn(monster, position)
monster:hazardCrit(hazard.crit)
monster:hazardDodge(hazard.dodge)
monster:hazardDamageBoost(hazard.damageBoost)
+ monster:hazardDefenseBoost(hazard.defenseBoost)
end
end
end
diff --git a/data/libs/raids_lib.lua b/data/libs/raids_lib.lua
index 9b0b9d105a7..9cf3039c43c 100644
--- a/data/libs/raids_lib.lua
+++ b/data/libs/raids_lib.lua
@@ -47,8 +47,8 @@ end
---Starts the raid if it can be started
---@param self Raid The raid to try to start
---@return boolean True if the raid was started, false otherwise
-function Raid:tryStart()
- if not self:canStart() then
+function Raid:tryStart(force)
+ if not force and not self:canStart() then
return false
end
logger.info("Starting raid {}", self.name)
@@ -65,50 +65,58 @@ function Raid:canStart()
logger.debug("Raid {} is already running", self.name)
return false
end
+ local forceTrigger = self.kv:get("trigger-when-possible")
+ if not forceTrigger then
+ local lastOccurrence = (self.kv:get("last-occurrence") or 0) * 1000
+ local currentTime = os.time() * 1000
+ if self.minGapBetween and lastOccurrence and currentTime - lastOccurrence < self.minGapBetween then
+ logger.debug("Raid {} occurred too recently (last: {} ago, min: {})", self.name, FormatDuration(currentTime - lastOccurrence), FormatDuration(self.minGapBetween))
+ return false
+ end
+
+ if not self.targetChancePerDay or not self.maxChancePerCheck then
+ logger.debug("Raid {} does not have a chance configured (targetChancePerDay: {}, maxChancePerCheck: {})", self.name, self.targetChancePerDay, self.maxChancePerCheck)
+ return false
+ end
+
+ local checksToday = tonumber(self.kv:get("checks-today") or 0)
+ if self.maxChecksPerDay and checksToday >= self.maxChecksPerDay then
+ logger.debug("Raid {} has already checked today (checks today: {}, max: {})", self.name, checksToday, self.maxChecksPerDay)
+ return false
+ end
+ self.kv:set("checks-today", checksToday + 1)
+
+ local failedAttempts = self.kv:get("failed-attempts") or 0
+ local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval)
+ local initialChance = self.initialChance or (self.targetChancePerDay / checksPerDay)
+ local chanceIncrease = math.max((self.targetChancePerDay - initialChance) / checksPerDay, 0)
+ local chance = initialChance + (chanceIncrease * failedAttempts)
+ if chance > self.maxChancePerCheck then
+ chance = self.maxChancePerCheck
+ end
+ chance = chance * 1000
+
+ -- offset the chance by 1000 to allow for fractional chances
+ local roll = math.random(100 * 1000)
+ if roll > chance then
+ logger.debug("Raid {} failed to start (roll: {}, chance: {}, failed attempts: {})", self.name, roll, chance, failedAttempts)
+ self.kv:set("failed-attempts", failedAttempts + 1)
+ return false
+ end
+ end
+
if self.allowedDays and not self:isAllowedDay() then
logger.debug("Raid {} is not allowed today ({})", self.name, os.date("%A"))
+ self.kv:set("trigger-when-possible", true)
return false
end
if self.minActivePlayers and self:getActivePlayerCount() < self.minActivePlayers then
logger.debug("Raid {} does not have enough players (active: {}, min: {})", self.name, self:getActivePlayerCount(), self.minActivePlayers)
- return false
- end
- local lastOccurrence = (self.kv:get("last-occurrence") or 0) * 1000
- local currentTime = os.time() * 1000
- if self.minGapBetween and lastOccurrence and currentTime - lastOccurrence < self.minGapBetween then
- logger.debug("Raid {} occurred too recently (last: {} ago, min: {})", self.name, FormatDuration(currentTime - lastOccurrence), FormatDuration(self.minGapBetween))
- return false
- end
-
- if not self.targetChancePerDay or not self.maxChancePerCheck then
- logger.debug("Raid {} does not have a chance configured (targetChancePerDay: {}, maxChancePerCheck: {})", self.name, self.targetChancePerDay, self.maxChancePerCheck)
+ self.kv:set("trigger-when-possible", true)
return false
end
- local checksToday = tonumber(self.kv:get("checks-today") or 0)
- if self.maxChecksPerDay and checksToday >= self.maxChecksPerDay then
- logger.debug("Raid {} has already checked today (checks today: {}, max: {})", self.name, checksToday, self.maxChecksPerDay)
- return false
- end
- self.kv:set("checks-today", checksToday + 1)
-
- local failedAttempts = self.kv:get("failed-attempts") or 0
- local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval)
- local initialChance = self.initialChance or (self.targetChancePerDay / checksPerDay)
- local chanceIncrease = (self.targetChancePerDay - initialChance) / checksPerDay
- local chance = initialChance + (chanceIncrease * failedAttempts)
- if chance > self.maxChancePerCheck then
- chance = self.maxChancePerCheck
- end
- chance = chance * 1000
-
- -- offset the chance by 1000 to allow for fractional chances
- local roll = math.random(100 * 1000)
- if roll > chance then
- logger.debug("Raid {} failed to start (roll: {}, chance: {}, failed attempts: {})", self.name, roll, chance, failedAttempts)
- self.kv:set("failed-attempts", failedAttempts + 1)
- return false
- end
+ self.kv:set("trigger-when-possible", false)
self.kv:set("failed-attempts", 0)
return true
end
@@ -153,7 +161,7 @@ function Raid:addBroadcast(message, type)
return self:addStage({
start = function()
self:broadcast(type, message)
- Webhook.sendMessage("Incoming raid", message, WEBHOOK_COLOR_RAID)
+ Webhook.sendMessage(":space_invader: " .. message, announcementChannels["raids"])
end,
})
end
diff --git a/data/modules/scripts/blessings/blessings.lua b/data/modules/scripts/blessings/blessings.lua
index c7c1ce42a72..5a8e5e56c9c 100644
--- a/data/modules/scripts/blessings/blessings.lua
+++ b/data/modules/scripts/blessings/blessings.lua
@@ -20,11 +20,11 @@ Blessings.Credits = {
}
Blessings.Config = {
- AdventurerBlessingLevel = 0, -- Free full bless until level
+ AdventurerBlessingLevel = configManager.getNumber(configKeys.ADVENTURERSBLESSING_LEVEL), -- Free full bless until level
HasToF = false, -- Enables/disables twist of fate
InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus
- SkulledDeathLoseStoreItem = true, -- Destroy all items on store when dying with red/blackskull
- InventoryGlowOnFiveBless = true, -- Glow in yellow inventory items when the player has 5 or more bless,
+ SkulledDeathLoseStoreItem = configManager.getBoolean(configKeys.SKULLED_DEATH_LOSE_STORE_ITEM), -- Destroy all items on store when dying with red/blackskull
+ InventoryGlowOnFiveBless = configManager.getBoolean(configKeys.INVENTORY_GLOW), -- Glow in yellow inventory items when the player has 5 or more bless,
Debug = false, -- Prin debug messages in console if enabled
}
@@ -260,7 +260,7 @@ Blessings.doAdventurerBlessing = function(player)
end
player:addMissingBless(true, true)
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You received adventurers blessings for you being level lower than " .. Blessings.Config.AdventurerBlessingLevel .. "!")
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have adventurer's blessings for being level lower than " .. Blessings.Config.AdventurerBlessingLevel .. "!")
player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE)
return true
end
diff --git a/data/modules/scripts/daily_reward/daily_reward.lua b/data/modules/scripts/daily_reward/daily_reward.lua
index de4116f7ed3..270aa3189da 100644
--- a/data/modules/scripts/daily_reward/daily_reward.lua
+++ b/data/modules/scripts/daily_reward/daily_reward.lua
@@ -207,14 +207,7 @@ end
-- Core functions
DailyReward.insertHistory = function(playerId, dayStreak, description)
- return db.query(string.format(
- "INSERT INTO `daily_reward_history`(`player_id`, `daystreak`, `timestamp`, \z
- `description`) VALUES (%s, %s, %s, %s)",
- playerId,
- dayStreak,
- os.time(),
- db.escapeString(description)
- ))
+ return db.query(string.format("INSERT INTO `daily_reward_history`(`player_id`, `daystreak`, `timestamp`, `description`) VALUES (%s, %s, %s, %s)", playerId, dayStreak, os.time(), db.escapeString(description)))
end
DailyReward.retrieveHistoryEntries = function(playerId)
@@ -224,8 +217,7 @@ DailyReward.retrieveHistoryEntries = function(playerId)
end
local entries = {}
- local resultId = db.storeQuery("SELECT * FROM `daily_reward_history` WHERE `player_id` = \z
- " .. player:getGuid() .. " ORDER BY `timestamp` DESC LIMIT 15;")
+ local resultId = db.storeQuery("SELECT * FROM `daily_reward_history` WHERE `player_id` = " .. player:getGuid() .. " ORDER BY `timestamp` DESC LIMIT 15;")
if resultId then
repeat
local entry = {
@@ -421,7 +413,7 @@ function Player.selectDailyReward(self, msg)
end
local rewardCount = dailyTable.freeAccount
- if self:isPremium() then
+ if (configManager.getBoolean(configKeys.VIP_SYSTEM_ENABLED) and self:isVip()) or self:isPremium() then
rewardCount = dailyTable.premiumAccount
end
@@ -453,8 +445,7 @@ function Player.selectDailyReward(self, msg)
end
if totalCounter > rewardCount then
- self:sendError("Something went wrong here, please restart this dialog.")
- return false
+ logger.info("Player with name {} is trying to get totalCounter: {} more than rewardCount: {}!", self:getName(), totalCounter, rewardCount)
end
if totalCounter ~= orderedCounter then
logger.error("Player with name {} is trying to get wrong daily reward", self:getName())
@@ -471,41 +462,21 @@ function Player.selectDailyReward(self, msg)
local description = ""
for k, v in ipairs(items) do
if dailyTable.itemCharges then
- for i = 1, v.count do
+ for i = 1, rewardCount do
local inboxItem = inbox:addItem(v.itemId, dailyTable.itemCharges) -- adding charges for each item
if inboxItem then
inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
end
end
else
- local inboxItem = inbox:addItem(v.itemId, v.count) -- adding single item w/o charges
+ local inboxItem = inbox:addItem(v.itemId, rewardCount) -- adding single item w/o charges
if inboxItem then
inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
end
end
- if k ~= columnsPicked then
- description = description .. "" .. v.count .. "x " .. ItemType(v.itemId):getName() .. ", "
- else
- description = description .. "" .. v.count .. "x " .. ItemType(v.itemId):getName() .. "."
- end
+ description = description .. "" .. rewardCount .. "x " .. ItemType(v.itemId):getName() .. (k ~= columnsPicked and ", " or ".")
end
-
dailyRewardMessage = "Picked items: " .. description
-
- -- elseif dailyTable.type == DAILY_REWARD_TYPE_STORAGE then
- -- local description = ""
- -- for i = 1, #reward.things do
- -- for j = 1, #reward.things[i].storages do
- -- self:setStorageValue(reward.things[i].storages[j].storageId, reward.things[i].storages[j].value)
- -- end
- -- if i ~= #reward.things then
- -- description = description .. reward.things[i].name .. ", "
- -- else
- -- description = description .. reward.things[i].name .. "."
- -- end
- -- end
- -- dailyRewardMessage = "Picked reward: " .. description)
- -- end
elseif dailyTable.type == DAILY_REWARD_TYPE_XP_BOOST then
self:setExpBoostStamina(self:getExpBoostStamina() + (rewardCount * 60))
self:setStoreXpBoost(50)
diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua
index afaffcb0b6a..f524932a4bd 100644
--- a/data/modules/scripts/gamestore/gamestore.lua
+++ b/data/modules/scripts/gamestore/gamestore.lua
@@ -6342,6 +6342,7 @@ GameStore.Categories = {
{
icons = { "Name_Change.png" },
name = "Character Name Change",
+ home = true,
price = 250,
id = 65002,
description = "Tired of your current character name? Purchase a new one!\n\n{character}\n{info} relog required after purchase to finalise the name change",
diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua
index 981db5cec6b..d33485c7091 100644
--- a/data/modules/scripts/gamestore/init.lua
+++ b/data/modules/scripts/gamestore/init.lua
@@ -34,6 +34,7 @@ GameStore.OfferTypes = {
OFFER_TYPE_HIRELING_OUTFIT = 24,
OFFER_TYPE_HUNTINGSLOT = 25,
OFFER_TYPE_ITEM_BED = 26,
+ OFFER_TYPE_ITEM_UNIQUE = 27,
}
GameStore.SubActions = {
@@ -97,6 +98,7 @@ function convertType(type)
[GameStore.OfferTypes.OFFER_TYPE_CHARGES] = GameStore.ConverType.SHOW_ITEM,
[GameStore.OfferTypes.OFFER_TYPE_HIRELING] = GameStore.ConverType.SHOW_HIRELING,
[GameStore.OfferTypes.OFFER_TYPE_ITEM_BED] = GameStore.ConverType.SHOW_NONE,
+ [GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE] = GameStore.ConverType.SHOW_ITEM,
}
if not types[type] then
@@ -294,8 +296,8 @@ function parseTransferableCoins(playerId, msg)
addPlayerEvent(sendStorePurchaseSuccessful, 550, playerId, "You have transfered " .. amount .. " coins to " .. reciver .. " successfully")
-- Adding history for both receiver/sender
- GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transferred you this amount.", amount, GameStore.CoinType.Coin)
- GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transferred this amount to " .. reciver, -1 * amount, GameStore.CoinType.Coin)
+ GameStore.insertHistory(accountId, GameStore.HistoryTypes.HISTORY_TYPE_NONE, player:getName() .. " transferred you this amount.", amount, GameStore.CoinType.Transferable)
+ GameStore.insertHistory(player:getAccountId(), GameStore.HistoryTypes.HISTORY_TYPE_NONE, "You transferred this amount to " .. reciver, -1 * amount, GameStore.CoinType.Transferable)
openStore(playerId)
end
@@ -318,14 +320,14 @@ function parseRequestStoreOffers(playerId, msg)
local oldProtocol = player:getClient().version < 1200
if oldProtocol then
- local categoryName = msg:getString()
- local category = GameStore.getCategoryByName(categoryName)
+ local stringParam = msg:getString()
+ local category = GameStore.getCategoryByName(stringParam)
if category then
addPlayerEvent(sendShowStoreOffersOnOldProtocol, 350, playerId, category)
end
elseif actionType == GameStore.ActionType.OPEN_CATEGORY then
- local categoryName = msg:getString()
- local category = GameStore.getCategoryByName(categoryName)
+ local stringParam = msg:getString()
+ local category = GameStore.getCategoryByName(stringParam)
if category then
addPlayerEvent(sendShowStoreOffers, 50, playerId, category)
end
@@ -428,8 +430,11 @@ function parseBuyStoreOffer(playerId, msg)
-- At this point the purchase is assumed to be formatted correctly
local offerPrice = offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST and GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)] or offer.price
local offerCoinType = offer.coinType
+ if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then
+ offerPrice = 0
+ end
-- Check if offer can be honored
- if not player:canPayForOffer(offerPrice, offerCoinType) then
+ if offerPrice > 0 and not player:canPayForOffer(offerPrice, offerCoinType) then
return queueSendStoreAlertToUser("You don't have enough coins. Your purchase has been cancelled.", 250, playerId)
end
@@ -438,7 +443,9 @@ function parseBuyStoreOffer(playerId, msg)
-- Handled errors have a code index and unhandled errors do not
local pcallOk, pcallError = pcall(function()
if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
- GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.movable)
+ GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner)
+ elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then
+ GameStore.processItemPurchase(player, offer.itemtype, offer.count, offer.moveable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_POUCH then
GameStore.processItemPurchase(player, offer.itemtype, offer.count)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then
@@ -452,7 +459,7 @@ function parseBuyStoreOffer(playerId, msg)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREMIUM then
GameStore.processPremiumPurchase(player, offer.id)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
- GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.movable)
+ GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.moveable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE then
GameStore.processHouseRelatedPurchase(player, offer)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT then
@@ -470,14 +477,12 @@ function parseBuyStoreOffer(playerId, msg)
GameStore.processExpBoostPuchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then
GameStore.processPreyThirdSlot(player)
- elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then
- GameStore.processTaskHuntingThirdSlot(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then
GameStore.processPreyBonusReroll(player, offer.count)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_TEMPLE then
GameStore.processTempleTeleportPurchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_CHARGES then
- GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.movable)
+ GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges, offer.moveable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING then
local hirelingName = msg:getString()
local sex = msg:getByte()
@@ -641,10 +646,16 @@ function Player.canBuyOffer(self, offer)
if disabled ~= 1 then
if offer.type == GameStore.OfferTypes.OFFER_TYPE_POUCH then
- local pounch = self:getItemById(23721, true)
- if pounch then
+ local pouch = self:getItemById(23721, true)
+ if pouch then
disabled = 1
- disabledReason = "You already have Loot Pouch."
+ disabledReason = "You already have a Loot Pouch."
+ end
+ elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then
+ local item = self:getItemById(offer.itemtype, true)
+ if item then
+ disabled = 1
+ disabledReason = "You already have a " .. ItemType(item:getId()):getName() .. "."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_BLESSINGS then
if self:getBlessingCount(offer.blessid) >= 5 then
@@ -864,9 +875,14 @@ function sendShowStoreOffers(playerId, category, redirectId)
xpBoostPrice = GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)]
end
+ nameLockPrice = nil
+ if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then
+ nameLockPrice = 0
+ end
+
msg:addU32(off.id)
msg:addU16(off.count)
- msg:addU32(xpBoostPrice or off.price)
+ msg:addU32(xpBoostPrice or nameLockPrice or off.price)
msg:addByte(off.coinType or 0x00)
msg:addByte((off.disabledReadonIndex ~= nil) and 1 or 0)
@@ -1087,7 +1103,7 @@ function sendStoreTransactionHistory(playerId, page, entriesPerPage)
msg:addByte(entry.mode) -- 0 = normal, 1 = gift, 2 = refund
msg:add32(entry.amount)
if not oldProtocol then
- msg:addByte(0x0) -- 0 = transferable tibia coin, 1 = normal tibia coin
+ msg:addByte(entry.type or 0x00) -- 0 = transferable tibia coin, 1 = normal tibia coin
end
msg:addString(entry.description, "sendStoreTransactionHistory - entry.description")
if not oldProtocol then
@@ -1110,8 +1126,7 @@ function sendStorePurchaseSuccessful(playerId, message)
msg:addString(message, "sendStorePurchaseSuccessful - message")
if oldProtocol then
-- Send all coins can be used for buy store offers
- local totalCoins = player:getTibiaCoins() + player:getTransferableCoins()
- msg:addU32(totalCoins)
+ msg:addU32(player:getTibiaCoins())
-- Send transferable coins can be used on transfer
msg:addU32(player:getTransferableCoins())
end
@@ -1165,8 +1180,7 @@ function sendUpdatedStoreBalances(playerId)
msg:addByte(0x01)
-- Send total of coins (transferable and normal coin)
- local totalCoins = player:getTibiaCoins() + player:getTransferableCoins()
- msg:addU32(totalCoins)
+ msg:addU32(player:getTibiaCoins())
msg:addU32(player:getTransferableCoins()) -- How many are Transferable
if not oldProtocol then
-- How many are reserved for a Character Auction
@@ -1366,8 +1380,8 @@ GameStore.canUseHirelingName = function(name)
local result = {
ability = false,
}
- if name:len() < 3 or name:len() > 14 then
- result.reason = "The length of the hireling name must be between 3 and 14 characters."
+ if name:len() < 3 or name:len() > 18 then
+ result.reason = "The length of the hireling name must be between 3 and 18 characters."
return result
end
@@ -1422,8 +1436,8 @@ GameStore.canChangeToName = function(name)
local result = {
ability = false,
}
- if name:len() < 3 or name:len() > 14 then
- result.reason = "The length of your new name must be between 3 and 14 characters."
+ if name:len() < 3 or name:len() > 18 then
+ result.reason = "The length of your new name must be between 3 and 18 characters."
return result
end
@@ -1493,39 +1507,21 @@ end
-- take a table {code = ..., message = ...} if the error is handled. When no code
-- index is present the error is assumed to be unhandled.
-function GameStore.processItemPurchase(player, offerId, offerCount, movable)
+function GameStore.processItemPurchase(player, offerId, offerCount, moveable, setOwner)
if player:getFreeCapacity() < ItemType(offerId):getWeight(offerCount) then
return error({ code = 0, message = "Please make sure you have free capacity to hold this item." })
end
- local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
- if inbox then
- for t = 1, offerCount do
- local inboxItem = inbox:addItem(offerId, offerCount or 1)
- if movable ~= true and inboxItem then
- inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
- end
- end
- else
- return error({ code = 0, message = "Please make sure you have free slots in your store inbox." })
+ for t = 1, offerCount do
+ player:addItemStoreInbox(offerId, offerCount or 1, moveable, setOwner)
end
end
-function GameStore.processChargesPurchase(player, itemtype, name, charges, movable)
+function GameStore.processChargesPurchase(player, itemtype, name, charges, moveable, setOwner)
if player:getFreeCapacity() < ItemType(itemtype):getWeight(1) then
return error({ code = 0, message = "Please make sure you have free capacity to hold this item." })
end
-
- local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
- if inbox then
- local inboxItem = inbox:addItem(itemtype, charges)
-
- if movable ~= true and inboxItem then
- inboxItem:setAttribute(ITEM_ATTRIBUTE_STORE, systemTime())
- end
- else
- return error({ code = 0, message = "Please make sure you have free slots in your store inbox." })
- end
+ player:addItemStoreInbox(itemtype, charges, moveable, setOwner)
end
function GameStore.processSingleBlessingPurchase(player, blessId, count)
@@ -1561,7 +1557,7 @@ function GameStore.processPremiumPurchase(player, offerId)
end
end
-function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, movable)
+function GameStore.processStackablePurchase(player, offerId, offerCount, offerName, moveable, setOwner)
local function isKegItem(itemId)
return itemId >= ITEM_KEG_START and itemId <= ITEM_KEG_END
end
@@ -1703,8 +1699,12 @@ function GameStore.processNameChangePurchase(player, offer, productType, newName
end
end
- local resultId = db.storeQuery("SELECT * FROM `players` WHERE `name` = " .. db.escapeString(newName) .. "")
- if resultId ~= false then
+ newName = newName:lower():trim():gsub("(%l)(%w*)", function(a, b)
+ return string.upper(a) .. b
+ end)
+
+ local normalizedName = Game.getNormalizedPlayerName(newName, true)
+ if normalizedName then
return error({ code = 1, message = "This name is already used, please try again!" })
end
@@ -1713,25 +1713,16 @@ function GameStore.processNameChangePurchase(player, offer, productType, newName
return error({ code = 1, message = result.reason })
end
- player:makeCoinTransaction(offer)
-
- local message = string.format("You have purchased %s for %d coins.", offer.name, offer.price)
+ local message, namelockReason = "", player:kv():get("namelock")
+ if not namelockReason then
+ player:makeCoinTransaction(offer)
+ message = string.format("You have purchased %s for %d coins.", offer.name, offer.price)
+ else
+ message = "Your character has been renamed successfully."
+ end
addPlayerEvent(sendStorePurchaseSuccessful, 500, playerId, message)
- newName = newName:lower():gsub("(%l)(%w*)", function(a, b)
- return string.upper(a) .. b
- end)
- db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. player:getGuid())
- message = "You have successfully changed you name, relogin!"
- addEvent(function()
- local player = Player(playerId)
- if not player then
- return false
- end
-
- player:remove()
- end, 1000)
- -- If not, we ask him to do!
+ player:changeName(newName)
else
return addPlayerEvent(sendRequestPurchaseData, 250, playerId, offer.id, GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE)
end
@@ -1777,7 +1768,9 @@ function GameStore.processPreyBonusReroll(player, offerCount)
end
function GameStore.processTempleTeleportPurchase(player)
- if player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT) or player:isPzLocked() then
+ local inPz = player:getTile():hasFlag(TILESTATE_PROTECTIONZONE)
+ local inFight = player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)
+ if not inPz and inFight then
return error({ code = 0, message = "You can't use temple teleport in fight!" })
end
@@ -1828,7 +1821,10 @@ function GameStore.processHirelingChangeNamePurchase(player, offer, productType,
local offerId = offer.id
if player:getClient().version < 1200 then
- return error({ code = 1, message = "You cannot buy hireling change name on client 10, please relog on client 12 and try again." })
+ return error({
+ code = 1,
+ message = "You cannot buy hireling change name on client 10, please relog on client 12 and try again.",
+ })
end
if productType == GameStore.ClientOfferTypes.CLIENT_STORE_OFFER_NAMECHANGE then
@@ -1854,7 +1850,10 @@ function GameStore.processHirelingChangeSexPurchase(player, offer)
local playerId = player:getId()
if player:getClient().version < 1200 then
- return error({ code = 1, message = "You cannot buy hireling change sex on client 10, please relog on client 12 and try again." })
+ return error({
+ code = 1,
+ message = "You cannot buy hireling change sex on client 10, please relog on client 12 and try again.",
+ })
end
local message = "Close the store window to select which hireling should have the sex changed."
@@ -1865,7 +1864,10 @@ end
function GameStore.processHirelingSkillPurchase(player, offer)
if player:getClient().version < 1200 then
- return error({ code = 1, message = "You cannot buy hireling skill on client 10, please relog on client 12 and try again." })
+ return error({
+ code = 1,
+ message = "You cannot buy hireling skill on client 10, please relog on client 12 and try again.",
+ })
end
player:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE)
@@ -1875,7 +1877,10 @@ end
function GameStore.processHirelingOutfitPurchase(player, offer)
if player:getClient().version < 1200 then
- return error({ code = 1, message = "You cannot buy hireling outfit on client 10, please relog on client 12 and try again." })
+ return error({
+ code = 1,
+ message = "You cannot buy hireling outfit on client 10, please relog on client 12 and try again.",
+ })
end
local outfitName = GetHirelingOutfitNameById(offer.id)
@@ -1888,16 +1893,14 @@ end
--==Player==--
-- Character auction coins
function Player.canRemoveCoins(self, coins)
- if self:getTibiaCoins() < coins then
- return false
- end
- return true
+ return self:getTibiaCoins() >= coins
end
function Player.removeCoinsBalance(self, coins)
if self:canRemoveCoins(coins) then
sendStoreBalanceUpdating(self:getId(), true)
- return self:removeTibiaCoins(coins)
+ self:removeTibiaCoins(coins)
+ return true
end
return false
@@ -1911,51 +1914,16 @@ function Player.addCoinsBalance(self, coins, update)
return true
end
--- Transferable + normal coin
-function Player.canRemoveAllCoins(self, coins)
- if self:getTibiaCoins() + self:getTransferableCoins() < coins then
- return false
- end
- return true
-end
-
---[[
- Removes a specified amount of coins from the player's inventory.
- @param coins (number) - The amount of coins to be removed.
- @return (boolean) - Returns true if the coins were successfully removed, false otherwise.
---]]
-function Player.removeAllCoins(self, coins)
- -- Check if it is possible to remove all the coins.
- if self:canRemoveAllCoins(coins) then
- local tibiaCoins = self:getTibiaCoins()
- -- Check if there are enough Tibia coins to remove.
- if tibiaCoins >= coins then
- self:removeTibiaCoins(coins)
- else
- -- Remove the available Tibia coins and calculate the remaining amount to remove from transferable coins.
- self:removeTibiaCoins(tibiaCoins)
- self:removeTransferableCoins(coins - tibiaCoins)
- end
-
- sendStoreBalanceUpdating(self:getId(), true)
- return true
- end
-
- return false
-end
-
-- Transferable coins
function Player.canRemoveTransferableCoins(self, coins)
- if self:getTransferableCoins() < coins then
- return false
- end
- return true
+ return self:getTransferableCoins() >= coins
end
function Player.removeTransferableCoinsBalance(self, coins)
if self:canRemoveTransferableCoins(coins) then
sendStoreBalanceUpdating(self:getId(), true)
- return self:removeTransferableCoins(coins)
+ self:removeTransferableCoins(coins)
+ return true
end
return false
@@ -1971,7 +1939,7 @@ end
--- Support Functions
function Player.makeCoinTransaction(self, offer, desc)
- local op = true
+ local op = false
if desc then
desc = offer.name .. " (" .. desc .. ")"
@@ -1979,14 +1947,9 @@ function Player.makeCoinTransaction(self, offer, desc)
desc = offer.name
end
- -- First try remove normal coins, later the transferable coins
- if self:canRemoveAllCoins(offer.price) then
- op = self:removeAllCoins(offer.price)
- elseif self:canRemoveCoins(offer.price) then
- -- Remove normal coins
+ if offer.coinType == GameStore.CoinType.Coin and self:canRemoveCoins(offer.price) then
op = self:removeCoinsBalance(offer.price)
- else
- -- Remove transferable coins
+ elseif offer.coinType == GameStore.CoinType.Transferable and self:canRemoveTransferableCoins(offer.price) then
op = self:removeTransferableCoinsBalance(offer.price)
end
@@ -2003,23 +1966,17 @@ end
-- @param coinType (string) - The type of the offer.
-- @return (boolean) - Returns true if the player can pay for the offer, false otherwise.
function Player.canPayForOffer(self, coinsToRemove, coinType)
- local can_remove_coins = self:canRemoveCoins(coinsToRemove)
- local can_remove_transferable_coins = self:canRemoveTransferableCoins(coinsToRemove)
-
-- Check if the player has the required amount of regular coins and the offer type is regular.
- if self:getTibiaCoins() >= coinsToRemove and coinType == GameStore.CoinType.Coin then
- return can_remove_coins
+ if coinType == GameStore.CoinType.Coin then
+ return self:canRemoveCoins(coinsToRemove)
end
-- Check if the player has the required amount of transferable coins and the offer type is transferable.
- if self:getTransferableCoins() >= coinsToRemove and coinType == GameStore.CoinType.Transferable then
- return can_remove_transferable_coins
+ if coinType == GameStore.CoinType.Transferable then
+ return self:canRemoveTransferableCoins(coinsToRemove)
end
- -- Check if the player has either the required amount of regular coins or transferable coins,
- -- or both amounts combined.
- local remove_all_coins = self:canRemoveAllCoins(coinsToRemove)
- return remove_all_coins or (can_remove_coins or can_remove_transferable_coins)
+ return false
end
--- Other players functions
@@ -2110,13 +2067,17 @@ function sendHomePage(playerId)
end
msg:addU16(#homeOffers) -- offers
-
for p, offer in pairs(homeOffers) do
+ local offerPrice = offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST and GameStore.ExpBoostValues[player:getStorageValue(GameStore.Storages.expBoostCount)] or offer.price
+ if offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE and player:kv():get("namelock") then
+ offerPrice = 0
+ end
+
msg:addString(offer.name, "sendHomePage - offer.name")
msg:addByte(0x1) -- ?
msg:addU32(offer.id or 0) -- id
msg:addU16(0x1)
- msg:addU32(offer.price)
+ msg:addU32(offerPrice)
msg:addByte(offer.coinType or 0x00)
msg:addByte((offer.disabledReadonIndex ~= nil) and 1 or 0)
@@ -2135,11 +2096,10 @@ function sendHomePage(playerId)
msg:addString(offer.icons[1], "sendHomePage - offer.icons[1]")
elseif type == GameStore.ConverType.SHOW_MOUNT then
local mount = Mount(offer.id)
- if mount then
- msg:addU16(mount:getClientId())
- else
- logger.debug("[sendHomePage] mount with id {} not exist, ignoring to avoid a debug on the client", offer.id)
+ if not mount then
msg:addU16(0)
+ else
+ msg:addU16(mount:getClientId())
end
elseif type == GameStore.ConverType.SHOW_ITEM then
msg:addU16(offer.itemtype)
@@ -2176,13 +2136,14 @@ function sendHomePage(playerId)
end
function Player:openStore(serviceName) --exporting the method so other scripts can use to open store
- openStore(self:getId())
+ local playerId = self:getId()
+ openStore(playerId)
--local serviceType = msg:getByte()
local category = GameStore.Categories and GameStore.Categories[1] or nil
if serviceName and serviceName:lower() == "home" then
- return sendHomePage(self:getId())
+ return sendHomePage(playerId)
end
if serviceName and GameStore.getCategoryByName(serviceName) then
diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua
index 9424ebf5785..985bbfcd3a3 100644
--- a/data/scripts/eventcallbacks/player/on_look.lua
+++ b/data/scripts/eventcallbacks/player/on_look.lua
@@ -16,6 +16,10 @@ function callback.playerOnLook(player, thing, position, distance)
else
description = description .. thing:getDescription(distance)
end
+ local ownerName = thing:getOwnerName()
+ if ownerName then
+ description = string.format("%s\nIt belongs to %s.", description, ownerName)
+ end
else
description = description .. thing:getDescription(distance)
if thing:isMonster() then
diff --git a/data/scripts/talkactions/gm/count_monsters.lua b/data/scripts/talkactions/gm/count_monsters.lua
index 1cd9744b908..25acfdea1ee 100644
--- a/data/scripts/talkactions/gm/count_monsters.lua
+++ b/data/scripts/talkactions/gm/count_monsters.lua
@@ -24,14 +24,19 @@ function count_monsters.onSay(player, words, param)
end
end
- writing_file:write("--- Total of monsters on server ---\n")
-
+ writing_file:write("--- Monsters on Server ---\n")
+ local total = 0
for monster, count in pairsByKeys(monsters) do
writing_file:write(monster .. " - " .. count .. "\n")
+ total = total + count
end
+ local outputMsg = "Total of monsters on server: " .. total
+ writing_file:write("\n" .. outputMsg .. "\n------------------------")
writing_file:close()
+ logger.info(outputMsg)
+
return true
end
diff --git a/data/scripts/talkactions/gm/namelock.lua b/data/scripts/talkactions/gm/namelock.lua
new file mode 100644
index 00000000000..1b204759e02
--- /dev/null
+++ b/data/scripts/talkactions/gm/namelock.lua
@@ -0,0 +1,48 @@
+local namelock = TalkAction("/namelock")
+
+function namelock.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ if param == "" then
+ player:sendCancelMessage("Command param required.")
+ return true
+ end
+
+ local name = param
+ local reason = ""
+
+ local separatorPos = param:find(",")
+ if separatorPos then
+ name = param:sub(0, separatorPos - 1)
+ reason = string.trim(param:sub(separatorPos + 1))
+ end
+
+ if reason == "" then
+ player:sendCancelMessage("You must specify a reason.")
+ return true
+ end
+
+ local target = Player(name)
+ local online = true
+ if not target then
+ target = Game.getOfflinePlayer(name)
+ online = false
+ end
+ if target and target:isPlayer() then
+ target:kv():set("namelock", reason)
+ local text = target:getName() .. " has been namelocked"
+ logger.info(text .. ", reason: " .. reason)
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, text)
+ Webhook.sendMessage("Player Namelocked", text .. " reason: " .. reason .. ".", WEBHOOK_COLOR_YELLOW, announcementChannels["serverAnnouncements"])
+ if online then
+ CheckNamelock(target)
+ end
+ else
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, name .. " was not found.")
+ end
+end
+
+namelock:separator(" ")
+namelock:groupType("gamemaster")
+namelock:register()
diff --git a/data/scripts/talkactions/god/inbox_command.lua b/data/scripts/talkactions/god/inbox_command.lua
index b9834aa2384..bfc902b1315 100644
--- a/data/scripts/talkactions/god/inbox_command.lua
+++ b/data/scripts/talkactions/god/inbox_command.lua
@@ -20,7 +20,7 @@ function inboxCommand.onSay(player, words, param)
end
end
elseif param[2] == "add" then
- inbox:addItem(tonumber(param[3]), 1, INDEX_WHEREEVER, FLAG_NOLIMIT)
+ target:addItemStoreInbox(tonumber(param[3]), 1, true, false)
player:say(tonumber(param[3]) .. " added")
end
end
diff --git a/data/scripts/talkactions/god/raids.lua b/data/scripts/talkactions/god/raids.lua
new file mode 100644
index 00000000000..15f21801293
--- /dev/null
+++ b/data/scripts/talkactions/god/raids.lua
@@ -0,0 +1,104 @@
+local startRaid = TalkAction("/raid")
+
+function startRaid.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ if param == "" then
+ player:sendCancelMessage("Command param required.")
+ return true
+ end
+
+ if Raid.registry[param] then
+ local raid = Raid.registry[param]
+ if raid:tryStart(true) then
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid " .. param .. " started.")
+ else
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid " .. param .. " could not be started.")
+ end
+ return true
+ end
+
+ local returnValue = Game.startRaid(param)
+ if returnValue ~= RETURNVALUE_NOERROR then
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, Game.getReturnMessage(returnValue))
+ else
+ player:sendTextMessage(MESSAGE_ADMINISTRADOR, "Raid started.")
+ end
+ return true
+end
+
+startRaid:separator(" ")
+startRaid:groupType("god")
+startRaid:register()
+
+local simulator = TalkAction("/simraid")
+
+function simulator.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+ -- initialChance,targetChancePerDay,maxChancePerCheck
+ local zone = Zone("raid.simzone")
+ local params = param:split(",")
+ local initialChance = tonumber(params[1])
+ local targetChancePerDay = tonumber(params[2])
+ local maxChancePerCheck = tonumber(params[3])
+ local raid = Raid("simraid", {
+ zone = zone,
+ allowedDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" },
+ minActivePlayers = 0,
+ initialChance = initialChance == 0 and nil or initialChance,
+ targetChancePerDay = targetChancePerDay,
+ maxChancePerCheck = maxChancePerCheck,
+ })
+ raid.kv:set("failed-attempts", 0)
+ raid.kv:set("trigger-when-possible", false)
+ raid.kv:set("last-occurrence", 0)
+ raid.kv:set("checks-today", 0)
+
+ local triggerCount = 0
+ local rolls = 0
+
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Simulating raid with initialChance=" .. initialChance .. ", targetChancePerDay=" .. targetChancePerDay .. ", maxChancePerCheck=" .. maxChancePerCheck .. "...")
+
+ local checksPerDay = ParseDuration("23h") / ParseDuration(Raid.checkInterval)
+ while triggerCount < 10 do
+ rolls = rolls + 1
+ if raid:tryStart() then
+ triggerCount = triggerCount + 1
+ raid:reset()
+ end
+ end
+
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Raid triggered " .. triggerCount .. " times in " .. rolls .. " rolls (" .. rolls / checksPerDay .. " days or once every " .. (rolls / checksPerDay) / triggerCount .. " days)")
+ return true
+end
+
+simulator:separator(" ")
+simulator:groupType("god")
+simulator:register()
+
+local listRaid = TalkAction("/listraid")
+
+function listRaid.onSay(player, words, param)
+ -- create log
+ logCommand(player, words, param)
+
+ local raids = {}
+ for name, raid in pairs(Raid.registry) do
+ table.insert(raids, name)
+ end
+ table.sort(raids)
+
+ local message = "Registered raids: "
+ for _, name in ipairs(raids) do
+ message = message .. name .. ", "
+ end
+ message = message:sub(1, -3)
+ player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message)
+ return true
+end
+
+listRaid:separator(" ")
+listRaid:groupType("god")
+listRaid:register()
diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp
index 541da679351..920494fea9c 100644
--- a/src/config/config_definitions.hpp
+++ b/src/config/config_definitions.hpp
@@ -36,6 +36,7 @@ enum ConfigKey_t : uint16_t {
CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES,
CLASSIC_ATTACK_SPEED,
CLEAN_PROTECTION_ZONES,
+ COMBAT_CHAIN_DELAY,
COMPRESSION_LEVEL,
CONVERT_UNSAFE_SCRIPTS,
CORE_DIRECTORY,
@@ -48,6 +49,8 @@ enum ConfigKey_t : uint16_t {
DEFAULT_PRIORITY,
DEPOTCHEST,
DEPOT_BOXES,
+ DISABLE_LEGACY_RAIDS,
+ DISABLE_MONSTER_ARMOR,
DISCORD_WEBHOOK_DELAY_MS,
DISCORD_WEBHOOK_URL,
EMOTE_SPELLS,
@@ -88,6 +91,7 @@ enum ConfigKey_t : uint16_t {
HAZARD_CRITICAL_INTERVAL,
HAZARD_CRITICAL_MULTIPLIER,
HAZARD_DAMAGE_MULTIPLIER,
+ HAZARD_DEFENSE_MULTIPLIER,
HAZARD_DODGE_MULTIPLIER,
HAZARD_EXP_BONUS_MULTIPLIER,
HAZARD_LOOT_BONUS_MULTIPLIER,
@@ -127,6 +131,8 @@ enum ConfigKey_t : uint16_t {
MAX_ALLOWED_ON_A_DUMMY,
MAX_CONTAINER,
MAX_CONTAINER_ITEM,
+ MAX_DAMAGE_REFLECTION,
+ MAX_ELEMENTAL_RESISTANCE,
MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER,
MAX_MESSAGEBUFFER,
MAX_PACKETS_PER_SECOND,
@@ -138,6 +144,7 @@ enum ConfigKey_t : uint16_t {
METRICS_ENABLE_PROMETHEUS,
METRICS_OSTREAM_INTERVAL,
METRICS_PROMETHEUS_ADDRESS,
+ MIN_ELEMENTAL_RESISTANCE,
MONTH_KILLS_TO_RED,
MULTIPLIER_ATTACKONFIST,
MYSQL_DB,
@@ -218,6 +225,7 @@ enum ConfigKey_t : uint16_t {
SCRIPTS_CONSOLE_LOGS,
SERVER_MOTD,
SERVER_NAME,
+ SKULLED_DEATH_LOSE_STORE_ITEM,
SORT_LOOT_BY_CHANCE,
SQL_PORT,
STAIRHOP_DELAY,
diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp
index 97d1052ad03..090fcaf73e8 100644
--- a/src/config/configmanager.cpp
+++ b/src/config/configmanager.cpp
@@ -80,6 +80,7 @@ bool ConfigManager::load() {
loadIntConfig(L, STASH_ITEMS, "stashItemCount", 5000);
loadBoolConfig(L, OLD_PROTOCOL, "allowOldProtocol", true);
+ loadBoolConfig(L, DISABLE_LEGACY_RAIDS, "disableLegacyRaids", false);
}
loadBoolConfig(L, ALLOW_CHANGEOUTFIT, "allowChangeOutfit", true);
@@ -97,6 +98,7 @@ bool ConfigManager::load() {
loadBoolConfig(L, CONVERT_UNSAFE_SCRIPTS, "convertUnsafeScripts", true);
loadBoolConfig(L, CLASSIC_ATTACK_SPEED, "classicAttackSpeed", false);
loadBoolConfig(L, TOGGLE_ATTACK_SPEED_ONFIST, "toggleAttackSpeedOnFist", false);
+ loadBoolConfig(L, DISABLE_MONSTER_ARMOR, "disableMonsterArmor", false);
loadIntConfig(L, MULTIPLIER_ATTACKONFIST, "multiplierSpeedOnFist", 5);
loadIntConfig(L, MAX_SPEED_ATTACKONFIST, "maxSpeedOnFist", 500);
loadBoolConfig(L, SCRIPTS_CONSOLE_LOGS, "showScriptsLogInConsole", true);
@@ -218,6 +220,7 @@ bool ConfigManager::load() {
loadIntConfig(L, CRITICALCHANCE, "criticalChance", 10);
loadIntConfig(L, ADVENTURERSBLESSING_LEVEL, "adventurersBlessingLevel", 21);
+ loadBoolConfig(L, SKULLED_DEATH_LOSE_STORE_ITEM, "skulledDeathLoseStoreItem", false);
loadIntConfig(L, FORGE_MAX_ITEM_TIER, "forgeMaxItemTier", 10);
loadIntConfig(L, FORGE_COST_ONE_SLIVER, "forgeCostOneSliver", 20);
loadIntConfig(L, FORGE_SLIVER_AMOUNT, "forgeSliverAmount", 3);
@@ -246,6 +249,7 @@ bool ConfigManager::load() {
loadFloatConfig(L, RATE_ATTACK_SPEED, "rateAttackSpeed", 1.0);
loadFloatConfig(L, RATE_OFFLINE_TRAINING_SPEED, "rateOfflineTrainingSpeed", 1.0);
loadFloatConfig(L, RATE_EXERCISE_TRAINING_SPEED, "rateExerciseTrainingSpeed", 1.0);
+ loadIntConfig(L, COMBAT_CHAIN_DELAY, "combatChainDelay", 50);
loadFloatConfig(L, RATE_MONSTER_HEALTH, "rateMonsterHealth", 1.0);
loadFloatConfig(L, RATE_MONSTER_ATTACK, "rateMonsterAttack", 1.0);
@@ -256,6 +260,10 @@ bool ConfigManager::load() {
loadIntConfig(L, BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN, "bossDefaultTimeToFightAgain", 20 * 60 * 60);
loadIntConfig(L, BOSS_DEFAULT_TIME_TO_DEFEAT, "bossDefaultTimeToDefeat", 20 * 60);
+ loadIntConfig(L, MIN_ELEMENTAL_RESISTANCE, "minElementalResistance", -200);
+ loadIntConfig(L, MAX_ELEMENTAL_RESISTANCE, "maxElementalResistance", 200);
+ loadIntConfig(L, MAX_DAMAGE_REFLECTION, "maxDamageReflection", 200);
+
loadFloatConfig(L, RATE_NPC_HEALTH, "rateNpcHealth", 1.0);
loadFloatConfig(L, RATE_NPC_ATTACK, "rateNpcAttack", 1.0);
loadFloatConfig(L, RATE_NPC_DEFENSE, "rateNpcDefense", 1.0);
@@ -297,6 +305,7 @@ bool ConfigManager::load() {
loadIntConfig(L, HAZARD_CRITICAL_CHANCE, "hazardCriticalChance", 750);
loadIntConfig(L, HAZARD_CRITICAL_MULTIPLIER, "hazardCriticalMultiplier", 25);
loadIntConfig(L, HAZARD_DAMAGE_MULTIPLIER, "hazardDamageMultiplier", 200);
+ loadIntConfig(L, HAZARD_DEFENSE_MULTIPLIER, "hazardDefenseMultiplier", 0);
loadIntConfig(L, HAZARD_DODGE_MULTIPLIER, "hazardDodgeMultiplier", 85);
loadIntConfig(L, HAZARD_PODS_DROP_MULTIPLIER, "hazardPodsDropMultiplier", 87);
loadIntConfig(L, HAZARD_PODS_TIME_TO_DAMAGE, "hazardPodsTimeToDamage", 2000);
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index a348f364078..e3211a1f52e 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -826,7 +826,7 @@ void Combat::combatTileEffects(const CreatureVector &spectators, std::shared_ptr
std::shared_ptr
- item = Item::CreateItem(itemId);
if (caster) {
- item->setAttribute(ItemAttribute_t::OWNER, caster->getID());
+ item->setOwner(caster);
}
ReturnValue ret = g_game().internalAddItem(tile, item);
@@ -937,15 +937,26 @@ bool Combat::doCombatChain(std::shared_ptr caster, std::shared_ptr(50, g_configManager().getNumber(COMBAT_CHAIN_DELAY, __FUNCTION__));
+ ++i;
for (auto to : toVector) {
auto nextTarget = g_game().getCreatureByID(to);
if (!nextTarget) {
continue;
}
- combat->doChainEffect(from, nextTarget->getPosition(), combat->params.chainEffect);
- combat->doCombat(caster, nextTarget, from);
+ g_dispatcher().scheduleEvent(
+ delay, [combat, caster, nextTarget, from, affected]() {
+ if (combat && caster && nextTarget) {
+ combat->doChainEffect(from, nextTarget->getPosition(), combat->params.chainEffect);
+ combat->doCombat(caster, nextTarget, from, affected);
+ }
+ },
+ "Combat::doCombatChain"
+ );
}
}
@@ -960,10 +971,11 @@ bool Combat::doCombat(std::shared_ptr caster, std::shared_ptrgetPosition() : Position());
}
-bool Combat::doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin) const {
+bool Combat::doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin, int affected /* = 1 */) const {
// target combat callback function
if (params.combatType != COMBAT_NONE) {
CombatDamage damage = getCombatDamage(caster, target);
+ damage.affected = affected;
if (damage.primary.type != COMBAT_MANADRAIN) {
doCombatHealth(caster, target, origin, damage, params);
} else {
@@ -1987,14 +1999,18 @@ void MagicField::onStepInField(const std::shared_ptr &creature) {
const ItemType &it = items[getID()];
if (it.conditionDamage) {
auto conditionCopy = it.conditionDamage->clone();
- auto ownerId = getAttribute(ItemAttribute_t::OWNER);
+ auto ownerId = getOwnerId();
if (ownerId) {
bool harmfulField = true;
auto itemTile = getTile();
if (g_game().getWorldType() == WORLD_TYPE_NO_PVP || itemTile && itemTile->hasFlag(TILESTATE_NOPVPZONE)) {
- std::shared_ptr owner = g_game().getCreatureByID(ownerId);
- if (owner) {
- if (owner->getPlayer() || (owner->isSummon() && owner->getMaster()->getPlayer())) {
+ auto ownerPlayer = g_game().getPlayerByGUID(ownerId);
+ if (ownerPlayer) {
+ harmfulField = false;
+ }
+ auto ownerCreature = g_game().getCreatureByID(ownerId);
+ if (ownerCreature) {
+ if (ownerCreature->getPlayer() || (ownerCreature->isSummon() && ownerCreature->getMaster()->getPlayer())) {
harmfulField = false;
}
}
@@ -2049,14 +2065,14 @@ void Combat::applyExtensions(std::shared_ptr caster, std::shared_ptrcritChance();
+ chance = monster->critChance() * 100;
}
bonus += damage.criticalDamage;
double multiplier = 1.0 + static_cast(bonus) / 100;
chance += (uint16_t)damage.criticalChance;
- if (chance != 0 && uniform_random(1, 100) <= chance) {
+ if (chance != 0 && uniform_random(1, 10000) <= chance) {
damage.critical = true;
damage.primary.value *= multiplier;
damage.secondary.value *= multiplier;
diff --git a/src/creatures/combat/combat.hpp b/src/creatures/combat/combat.hpp
index 2bb2d0a3589..71554b3761d 100644
--- a/src/creatures/combat/combat.hpp
+++ b/src/creatures/combat/combat.hpp
@@ -289,7 +289,7 @@ class Combat {
static void addDistanceEffect(std::shared_ptr caster, const Position &fromPos, const Position &toPos, uint16_t effect);
bool doCombat(std::shared_ptr caster, std::shared_ptr target) const;
- bool doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin) const;
+ bool doCombat(std::shared_ptr caster, std::shared_ptr target, const Position &origin, int affected = 1) const;
bool doCombat(std::shared_ptr caster, const Position &pos) const;
bool setCallback(CallBackParam_t key);
diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp
index d684b5433ff..152bc37c496 100644
--- a/src/creatures/combat/spells.cpp
+++ b/src/creatures/combat/spells.cpp
@@ -565,7 +565,7 @@ bool Spell::playerRuneSpellCheck(std::shared_ptr player, const Position
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
g_game().addMagicEffect(player->getPosition(), CONST_ME_POFF);
return false;
- } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) {
+ } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID) && !topVisibleCreature) {
player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM);
g_game().addMagicEffect(player->getPosition(), CONST_ME_POFF);
return false;
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index 4720b903233..8e2e748c004 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -800,23 +800,23 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared
corpse->startDecaying();
bool corpses = corpse->isRewardCorpse() || (corpse->getID() == ITEM_MALE_CORPSE || corpse->getID() == ITEM_FEMALE_CORPSE);
const auto player = mostDamageCreature ? mostDamageCreature->getPlayer() : nullptr;
- if (corpse->getContainer() && player && !corpses) {
+ auto corpseContainer = corpse->getContainer();
+ if (corpseContainer && player && !corpses) {
auto monster = getMonster();
if (monster && !monster->isRewardBoss()) {
std::ostringstream lootMessage;
- lootMessage << "Loot of " << getNameDescription() << ": " << corpse->getContainer()->getContentDescription(player->getProtocolVersion() < 1200) << ".";
- auto suffix = corpse->getContainer()->getAttribute(ItemAttribute_t::LOOTMESSAGE_SUFFIX);
+ lootMessage << "Loot of " << getNameDescription() << ": " << corpseContainer->getContentDescription(player->getProtocolVersion() < 1200) << ".";
+ auto suffix = corpseContainer->getAttribute(ItemAttribute_t::LOOTMESSAGE_SUFFIX);
if (!suffix.empty()) {
lootMessage << suffix;
}
player->sendLootMessage(lootMessage.str());
}
- if (player->checkAutoLoot()) {
- int32_t pos = tile->getStackposOfItem(player, corpse);
+ if (player->checkAutoLoot() && corpseContainer && mostDamageCreature->getPlayer()) {
g_dispatcher().addEvent(
- std::bind(&Game::playerQuickLoot, &g_game(), mostDamageCreature->getID(), this->getPosition(), corpse->getID(), pos - 1, nullptr, false, true),
- "Game::playerQuickLoot"
+ std::bind(&Game::playerQuickLootCorpse, &g_game(), player, corpseContainer),
+ "Game::playerQuickLootCorpse"
);
}
}
@@ -937,6 +937,11 @@ BlockType_t Creature::blockHit(std::shared_ptr attacker, CombatType_t
// Apply skills 12.72 absorbs damage
applyAbsorbDamageModifications(attacker, damage, combatType);
+ if (getMonster() && g_configManager().getBoolean(DISABLE_MONSTER_ARMOR, __FUNCTION__)) {
+ checkDefense = false;
+ checkArmor = false;
+ }
+
if (isImmune(combatType)) {
damage = 0;
blockType = BLOCK_IMMUNITY;
@@ -984,6 +989,9 @@ BlockType_t Creature::blockHit(std::shared_ptr attacker, CombatType_t
mitigateDamage(combatType, blockType, damage);
+ if (damage != 0) {
+ onTakeDamage(attacker, damage);
+ }
onAttacked();
return blockType;
}
diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp
index 2052e7a2866..cbbdc94e3ac 100644
--- a/src/creatures/creature.hpp
+++ b/src/creatures/creature.hpp
@@ -150,6 +150,14 @@ class Creature : virtual public Thing, public SharedObject {
moveLocked = locked;
}
+ bool isDirectionLocked() const {
+ return directionLocked;
+ }
+
+ void setDirectionLocked(bool locked) {
+ directionLocked = locked;
+ }
+
int32_t getThrowRange() const override final {
return 1;
}
@@ -449,6 +457,7 @@ class Creature : virtual public Thing, public SharedObject {
virtual void onGainExperience(uint64_t gainExp, std::shared_ptr target);
virtual void onAttackedCreatureBlockHit(BlockType_t) { }
virtual void onBlockHit() { }
+ virtual void onTakeDamage(std::shared_ptr, int32_t) { }
virtual void onChangeZone(ZoneType_t zone);
virtual void onAttackedCreatureChangeZone(ZoneType_t zone);
virtual void onIdleStatus();
@@ -773,6 +782,7 @@ class Creature : virtual public Thing, public SharedObject {
bool floorChange = false;
bool canUseDefense = true;
bool moveLocked = false;
+ bool directionLocked = false;
bool hasFollowPath = false;
int8_t charmChanceModifier = 0;
diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp
index f4acbc1f39c..35365a609dc 100644
--- a/src/creatures/monsters/monster.cpp
+++ b/src/creatures/monsters/monster.cpp
@@ -486,10 +486,11 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL
if (++it != resultList.end()) {
const Position &targetPosition = getTarget->getPosition();
int32_t minRange = std::max(Position::getDistanceX(myPos, targetPosition), Position::getDistanceY(myPos, targetPosition));
+ int32_t factionOffset = static_cast(getTarget->getFaction()) * 100;
do {
const Position &pos = (*it)->getPosition();
- int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos));
+ int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)) + factionOffset;
if (distance < minRange) {
getTarget = *it;
minRange = distance;
@@ -504,7 +505,8 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL
}
const Position &pos = creature->getPosition();
- int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos));
+ int32_t factionOffset = static_cast(getTarget->getFaction()) * 100;
+ int32_t distance = std::max(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos)) + factionOffset;
if (distance < minRange) {
getTarget = creature;
minRange = distance;
@@ -523,12 +525,14 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL
auto it = resultList.begin();
getTarget = *it;
if (++it != resultList.end()) {
- int32_t minHp = getTarget->getHealth();
+ int32_t factionOffset = static_cast(getTarget->getFaction()) * 100000;
+ int32_t minHp = getTarget->getHealth() + factionOffset;
do {
- if ((*it)->getHealth() < minHp) {
+ auto hp = (*it)->getHealth() + factionOffset;
+ factionOffset = static_cast((*it)->getFaction()) * 100000;
+ if (hp < minHp) {
getTarget = *it;
-
- minHp = getTarget->getHealth();
+ minHp = hp;
}
} while (++it != resultList.end());
}
@@ -546,9 +550,10 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL
if (++it != resultList.end()) {
int32_t mostDamage = 0;
do {
+ int32_t factionOffset = static_cast((*it)->getFaction()) * 100000;
const auto dmg = damageMap.find((*it)->getID());
if (dmg != damageMap.end()) {
- if (dmg->second.total > mostDamage) {
+ if (dmg->second.total + factionOffset > mostDamage) {
mostDamage = dmg->second.total;
getTarget = *it;
}
@@ -584,6 +589,14 @@ void Monster::onFollowCreatureComplete(const std::shared_ptr &creature
}
}
+float Monster::getMitigation() const {
+ float mitigation = mType->info.mitigation * getDefenseMultiplier();
+ if (g_configManager().getBoolean(DISABLE_MONSTER_ARMOR, __FUNCTION__)) {
+ mitigation += std::ceil(static_cast(getDefense() + getArmor()) / 100.f) * getDefenseMultiplier() * 2.f;
+ }
+ return std::min(mitigation, 30.f);
+}
+
BlockType_t Monster::blockHit(std::shared_ptr 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);
@@ -1075,7 +1088,7 @@ void Monster::pushItems(std::shared_ptr tile, const Direction &nextDirecti
auto it = items->begin();
while (it != items->end()) {
std::shared_ptr
- item = *it;
- if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->getAttribute(ItemAttribute_t::ACTIONID) != IMMOVABLE_ACTION_ID) {
+ if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID)) && item->canBeMoved()) {
if (moveCount < 20 && pushItem(item, nextDirection)) {
++moveCount;
} else if (!item->isCorpse() && g_game().internalRemoveItem(item) == RETURNVALUE_NOERROR) {
diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp
index b6194543099..b4b3337bbed 100644
--- a/src/creatures/monsters/monster.hpp
+++ b/src/creatures/monsters/monster.hpp
@@ -73,9 +73,7 @@ class Monster final : public Creature {
RaceType_t getRace() const override {
return mType->info.race;
}
- float getMitigation() const override {
- return mType->info.mitigation * getDefenseMultiplier();
- }
+ float getMitigation() const override;
int32_t getArmor() const override {
return mType->info.armor * getDefenseMultiplier();
}
@@ -272,6 +270,12 @@ class Monster final : public Creature {
void setHazardSystemDamageBoost(bool value) {
hazardDamageBoost = value;
}
+ bool getHazardSystemDefenseBoost() const {
+ return hazardDefenseBoost;
+ }
+ void setHazardSystemDefenseBoost(bool value) {
+ hazardDefenseBoost = value;
+ }
// Hazard end
void updateTargetList();
@@ -391,6 +395,7 @@ class Monster final : public Creature {
bool hazardCrit = false;
bool hazardDodge = false;
bool hazardDamageBoost = false;
+ bool hazardDefenseBoost = false;
void onCreatureEnter(std::shared_ptr creature);
void onCreatureLeave(std::shared_ptr creature);
diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp
index bb911f2cce6..ab18ca6c9d6 100644
--- a/src/creatures/monsters/monsters.hpp
+++ b/src/creatures/monsters/monsters.hpp
@@ -66,7 +66,8 @@ class MonsterType {
std::vector voiceVector;
std::vector lootItems;
- std::vector scripts;
+ // We need to keep the order of scripts, so we use a set isntead of an unordered_set
+ std::set scripts;
std::vector attackSpells;
std::vector defenseSpells;
std::vector summons;
diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp
index 05435d6b4a8..c0f2fb382e8 100644
--- a/src/creatures/monsters/spawns/spawn_monster.cpp
+++ b/src/creatures/monsters/spawns/spawn_monster.cpp
@@ -149,6 +149,8 @@ SpawnMonster::~SpawnMonster() {
for (const auto &[_, monster] : spawnedMonsterMap) {
monster->setSpawnMonster(nullptr);
}
+ stopEvent();
+ spawnMonsterMap.clear();
}
bool SpawnMonster::findPlayer(const Position &pos) {
@@ -365,13 +367,23 @@ void SpawnMonster::removeMonster(std::shared_ptr monster) {
spawnedMonsterMap.erase(spawnMonsterId);
}
+void SpawnMonster::removeMonsters() {
+ spawnMonsterMap.clear();
+ spawnedMonsterMap.clear();
+}
+
void SpawnMonster::setMonsterVariant(const std::string &variant) {
for (auto &it : spawnMonsterMap) {
std::unordered_map, uint32_t> monsterTypes;
for (const auto &[monsterType, weight] : it.second.monsterTypes) {
- auto variantName = variant + monsterType->typeName;
- auto variantType = g_monsters().getMonsterType(variantName, false);
- monsterTypes.emplace(variantType, weight);
+ if (!monsterType || monsterType->typeName.empty()) {
+ continue;
+ }
+ auto variantName = variant + "|" + monsterType->typeName;
+ auto variantType = g_monsters().getMonsterType(variantName, true);
+ if (variantType) {
+ monsterTypes.emplace(variantType, weight);
+ }
}
it.second.monsterTypes = monsterTypes;
}
diff --git a/src/creatures/monsters/spawns/spawn_monster.hpp b/src/creatures/monsters/spawns/spawn_monster.hpp
index 9ea29e4f317..7e7f7cf3f49 100644
--- a/src/creatures/monsters/spawns/spawn_monster.hpp
+++ b/src/creatures/monsters/spawns/spawn_monster.hpp
@@ -38,6 +38,7 @@ class SpawnMonster {
bool addMonster(const std::string &name, const Position &pos, Direction dir, uint32_t interval, uint32_t weight = 1);
void removeMonster(std::shared_ptr monster);
+ void removeMonsters();
uint32_t getInterval() const {
return interval;
@@ -82,6 +83,10 @@ class SpawnsMonster {
bool loadFromXML(const std::string &filemonstername);
void startup();
void clear();
+ SpawnMonster &addSpawnMonster(const Position &pos, int32_t radius) {
+ spawnMonsterList.emplace_front(pos, radius);
+ return spawnMonsterList.front();
+ }
bool isStarted() const {
return started;
diff --git a/src/creatures/npcs/npcs.hpp b/src/creatures/npcs/npcs.hpp
index d836e6e9252..48170a94fd4 100644
--- a/src/creatures/npcs/npcs.hpp
+++ b/src/creatures/npcs/npcs.hpp
@@ -66,7 +66,8 @@ class NpcType : public SharedObject {
std::vector soundVector;
std::vector voiceVector;
- std::vector scripts;
+ // We need to keep the order of scripts, so we use a set isntead of an unordered_set
+ std::set scripts;
std::vector shopItemVector;
NpcsEvent_t eventType = NPCS_EVENT_NONE;
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index 47789f5ba18..867b32dbefb 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -2482,6 +2482,9 @@ void Player::onBlockHit() {
}
}
+void Player::onTakeDamage(std::shared_ptr attacker, int32_t damage) {
+}
+
void Player::onAttackedCreatureBlockHit(BlockType_t blockType) {
lastAttackBlockType = blockType;
@@ -2742,24 +2745,28 @@ void Player::death(std::shared_ptr lastHitCreature) {
}
sendTextMessage(MESSAGE_EVENT_ADVANCE, deathType.str());
+ auto adventurerBlessingLevel = g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__);
+ auto willNotLoseBless = getLevel() < adventurerBlessingLevel && getVocationId() > VOCATION_NONE;
+
std::string bless = getBlessingsName();
- std::ostringstream blesses;
- if (bless.length() == 0) {
- blesses << "You weren't protected with any blessings.";
+ std::ostringstream blessOutput;
+ if (willNotLoseBless) {
+ blessOutput << fmt::format("You still have adventurer's blessings for being level lower than {}!", adventurerBlessingLevel);
} else {
- blesses << "You were blessed with " << bless;
- }
- sendTextMessage(MESSAGE_EVENT_ADVANCE, blesses.str());
+ bless.empty() ? blessOutput << "You weren't protected with any blessings."
+ : blessOutput << "You were blessed with " << bless;
- // Make player lose bless
- uint8_t maxBlessing = 8;
- if (pvpDeath && hasBlessing(1)) {
- removeBlessing(1, 1); // Remove TOF only
- } else {
- for (int i = 2; i <= maxBlessing; i++) {
- removeBlessing(i, 1);
+ // Make player lose bless
+ uint8_t maxBlessing = 8;
+ if (pvpDeath && hasBlessing(1)) {
+ removeBlessing(1, 1); // Remove TOF only
+ } else {
+ for (int i = 2; i <= maxBlessing; i++) {
+ removeBlessing(i, 1);
+ }
}
}
+ sendTextMessage(MESSAGE_EVENT_ADVANCE, blessOutput.str());
sendStats();
sendSkills();
@@ -2962,8 +2969,7 @@ void Player::notifyStatusChange(std::shared_ptr loginPlayer, VipStatus_t
return;
}
- auto it = VIPList.find(loginPlayer->guid);
- if (it == VIPList.end()) {
+ if (!VIPList.contains(loginPlayer->guid)) {
return;
}
@@ -2979,10 +2985,11 @@ void Player::notifyStatusChange(std::shared_ptr loginPlayer, VipStatus_t
}
bool Player::removeVIP(uint32_t vipGuid) {
- if (VIPList.erase(vipGuid) == 0) {
+ if (!VIPList.erase(vipGuid)) {
return false;
}
+ VIPList.erase(vipGuid);
if (account) {
IOLoginData::removeVIPEntry(account->getID(), vipGuid);
}
@@ -2996,8 +3003,7 @@ bool Player::addVIP(uint32_t vipGuid, const std::string &vipName, VipStatus_t st
return false;
}
- auto result = VIPList.insert(vipGuid);
- if (!result.second) {
+ if (!VIPList.insert(vipGuid).second) {
sendTextMessage(MESSAGE_FAILURE, "This player is already in your list.");
return false;
}
@@ -3079,6 +3085,9 @@ ReturnValue Player::queryAdd(int32_t index, const std::shared_ptr &thing,
g_logger().error("[Player::queryAdd] - Item is nullptr");
return RETURNVALUE_NOTPOSSIBLE;
}
+ if (item->hasOwner() && !item->isOwner(getPlayer())) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags);
if (childIsOwner) {
@@ -4629,8 +4638,7 @@ bool Player::onKilledPlayer(const std::shared_ptr &target, bool lastHit)
for (auto &kill : target->unjustifiedKills) {
if (kill.target == getGUID() && kill.unavenged) {
kill.unavenged = false;
- auto it = attackedSet.find(target->guid);
- attackedSet.erase(it);
+ attackedSet.erase(target->guid);
break;
}
}
@@ -5038,7 +5046,7 @@ bool Player::hasAttacked(std::shared_ptr attacked) const {
return false;
}
- return attackedSet.find(attacked->guid) != attackedSet.end();
+ return attackedSet.contains(attacked->guid);
}
void Player::addAttacked(std::shared_ptr attacked) {
@@ -5046,7 +5054,7 @@ void Player::addAttacked(std::shared_ptr attacked) {
return;
}
- attackedSet.insert(attacked->guid);
+ attackedSet.emplace(attacked->guid);
}
void Player::removeAttacked(std::shared_ptr attacked) {
@@ -5054,10 +5062,7 @@ void Player::removeAttacked(std::shared_ptr attacked) {
return;
}
- auto it = attackedSet.find(attacked->guid);
- if (it != attackedSet.end()) {
- attackedSet.erase(it);
- }
+ attackedSet.erase(attacked->guid);
}
void Player::clearAttacked() {
@@ -7725,6 +7730,17 @@ void Player::parseAttackDealtHazardSystem(CombatDamage &damage, std::shared_ptr<
return;
}
}
+ if (monster->getHazardSystemDefenseBoost()) {
+ stage = points * static_cast(g_configManager().getNumber(HAZARD_DEFENSE_MULTIPLIER, __FUNCTION__));
+ if (stage != 0) {
+ damage.exString = "(hazard -" + std::to_string(stage / 100) + "%)";
+ damage.primary.value -= static_cast(std::ceil((static_cast(damage.primary.value) * stage) / 10000));
+ if (damage.secondary.value != 0) {
+ damage.secondary.value -= static_cast(std::ceil((static_cast(damage.secondary.value) * stage) / 10000));
+ }
+ return;
+ }
+ }
}
/*******************************************************************************
@@ -7741,6 +7757,7 @@ const std::unique_ptr &Player::wheel() const {
}
void Player::sendLootMessage(const std::string &message) const {
+ auto party = getParty();
if (!party) {
sendTextMessage(MESSAGE_LOOT, message);
return;
@@ -7799,3 +7816,30 @@ bool Player::hasPermittedConditionInPZ() const {
return hasPermittedCondition;
}
+
+void Player::checkAndShowBlessingMessage() {
+ auto adventurerBlessingLevel = g_configManager().getNumber(ADVENTURERSBLESSING_LEVEL, __FUNCTION__);
+ auto willNotLoseBless = getLevel() < adventurerBlessingLevel && getVocationId() > VOCATION_NONE;
+ std::string bless = getBlessingsName();
+ std::ostringstream blessOutput;
+
+ if (willNotLoseBless) {
+ auto addedBless = false;
+ for (uint8_t i = 2; i <= 6; i++) {
+ if (!hasBlessing(i)) {
+ addBlessing(i, 1);
+ addedBless = true;
+ }
+ }
+ sendBlessStatus();
+ if (addedBless) {
+ blessOutput << fmt::format("You have received adventurer's blessings for being level lower than {}!\nYou are still blessed with {}", adventurerBlessingLevel, bless);
+ }
+ } else {
+ bless.empty() ? blessOutput << "You lost all your blessings." : blessOutput << "You are still blessed with " << bless;
+ }
+
+ if (!blessOutput.str().empty()) {
+ sendTextMessage(MESSAGE_EVENT_ADVANCE, blessOutput.str());
+ }
+}
diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp
index a33c538c814..d267f118049 100644
--- a/src/creatures/players/player.hpp
+++ b/src/creatures/players/player.hpp
@@ -936,6 +936,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
void onGainSharedExperience(uint64_t gainExp, std::shared_ptr target);
void onAttackedCreatureBlockHit(BlockType_t blockType) override;
void onBlockHit() override;
+ void onTakeDamage(std::shared_ptr attacker, int32_t damage) override;
void onChangeZone(ZoneType_t zone) override;
void onAttackedCreatureChangeZone(ZoneType_t zone) override;
void onIdleStatus() override;
@@ -2518,7 +2519,7 @@ class Player final : public Creature, public Cylinder, public Bankable {
}
bool checkAutoLoot() const {
- const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__) && getStorageValue(STORAGEVALUE_AUTO_LOOT) > 0;
+ const bool autoLoot = g_configManager().getBoolean(AUTOLOOT, __FUNCTION__) && getStorageValue(STORAGEVALUE_AUTO_LOOT) != 0;
if (g_configManager().getBoolean(VIP_SYSTEM_ENABLED, __FUNCTION__) && g_configManager().getBoolean(VIP_AUTOLOOT_VIP_ONLY, __FUNCTION__)) {
return autoLoot && isVip();
}
@@ -2952,4 +2953,6 @@ class Player final : public Creature, public Cylinder, public Bankable {
void removeEmptyRewards();
bool hasOtherRewardContainerOpen(const std::shared_ptr container) const;
+
+ void checkAndShowBlessingMessage();
};
diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp
index cf07955afe7..a60d8124a76 100644
--- a/src/creatures/players/wheel/player_wheel.cpp
+++ b/src/creatures/players/wheel/player_wheel.cpp
@@ -1400,9 +1400,7 @@ void PlayerWheel::loadDedicationAndConvictionPerks() {
if (it != wheelFunctions.end()) {
internalData = it->second;
}
- if (internalData == nullptr) {
- g_logger().warn("[{}] 'internalData' cannot be null on slot type: {}, for player: {}", __FUNCTION__, i, m_player.getName());
- } else {
+ if (internalData) {
internalData(m_player.getPlayer(), points, vocationCipId, m_playerBonusData);
}
}
@@ -1816,7 +1814,7 @@ bool PlayerWheel::checkDivineEmpowerment() {
int32_t damageBonus = 0;
bool isOwner = false;
for (const auto &item : *items) {
- if (item->getID() == ITEM_DIVINE_EMPOWERMENT && item->getAttribute(ItemAttribute_t::OWNER) == m_player.getID()) {
+ if (item->getID() == ITEM_DIVINE_EMPOWERMENT && item->isOwner(m_player.getGUID())) {
isOwner = true;
break;
}
diff --git a/src/game/game.cpp b/src/game/game.cpp
index d741b24b1a2..9d8d89315ce 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -111,7 +111,7 @@ namespace InternalGame {
auto realItemParent = item->getRealParent();
auto isItemInGuestInventory = realItemParent && (realItemParent == player || realItemParent->getContainer());
- if (isGuest && !isItemInGuestInventory && !item->isLadder()) {
+ if (isGuest && !isItemInGuestInventory && !item->isLadder() && !item->canBeUsedByGuests()) {
return false;
}
}
@@ -136,7 +136,7 @@ namespace InternalGame {
auto targetItem = targetThing ? targetThing->getItem() : nullptr;
uint16_t targetId = targetItem ? targetItem->getID() : 0;
auto invitedCheckUseWith = house && item->getRealParent() && item->getRealParent() != player && (!house->isInvited(player) || house->getHouseAccessLevel(player) == HOUSE_GUEST);
- if (targetId != 0 && targetItem && !targetItem->isDummy() && invitedCheckUseWith) {
+ if (targetId != 0 && targetItem && invitedCheckUseWith && !item->canBeUsedByGuests()) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
return false;
}
@@ -748,7 +748,7 @@ std::shared_ptr Game::getNpcByName(const std::string &s) {
return nullptr;
}
-std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOffline /* = false */) {
+std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOffline /* = false */, bool isNewName /* = false */) {
if (s.empty()) {
return nullptr;
}
@@ -760,7 +760,11 @@ std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOf
}
std::shared_ptr tmpPlayer = std::make_shared(nullptr);
if (!IOLoginData::loadPlayerByName(tmpPlayer, s)) {
- g_logger().error("Failed to load player {} from database", s);
+ if (!isNewName) {
+ g_logger().error("Failed to load player {} from database", s);
+ } else {
+ g_logger().info("New name {} is available", s);
+ }
return nullptr;
}
tmpPlayer->setOnline(false);
@@ -1555,7 +1559,7 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo
}
}
- if (item->isWrapable()) {
+ if (item->isWrapable() || item->isStoreItem() || (item->hasOwner() && !item->isOwner(player))) {
auto toHouseTile = map.getTile(mapToPos)->dynamic_self_cast();
auto fromHouseTile = map.getTile(mapFromPos)->dynamic_self_cast();
if (fromHouseTile && (!toHouseTile || toHouseTile->getHouse()->getId() != fromHouseTile->getHouse()->getId())) {
@@ -1610,7 +1614,7 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s
}
// prevent move up
- if (!item->isStoreItem() && fromCylinder->getContainer() && fromCylinder->getContainer()->getID() == ITEM_GOLD_POUCH) {
+ if (!item->canBeMovedToStore() && fromCylinder->getContainer() && fromCylinder->getContainer()->getID() == ITEM_GOLD_POUCH) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
@@ -1620,7 +1624,7 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s
std::shared_ptr topParentContainer = toCylinderContainer->getRootContainer();
const auto parentContainer = topParentContainer->getParent() ? topParentContainer->getParent()->getContainer() : nullptr;
auto isStoreInbox = parentContainer && parentContainer->isStoreInbox();
- if (!item->isStoreItem() && (containerID == ITEM_STORE_INBOX || isStoreInbox)) {
+ if (!item->canBeMovedToStore() && (containerID == ITEM_STORE_INBOX || isStoreInbox)) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
@@ -1647,6 +1651,10 @@ ReturnValue Game::checkMoveItemToCylinder(std::shared_ptr player, std::s
if (!isValidMoveItem) {
return RETURNVALUE_NOTPOSSIBLE;
}
+
+ if (item->hasOwner() && !item->isOwner(player)) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
}
if (item->getContainer() && !item->isStoreItem()) {
@@ -1727,8 +1735,15 @@ ReturnValue Game::internalMoveItem(std::shared_ptr fromCylinder, std::
count = item->getItemCount();
}
+ // check if we can remove this item (using count of 1 since we don't know how
+ // much we can move yet)
+ ReturnValue ret = fromCylinder->queryRemove(item, 1, flags, actor);
+ if (ret != RETURNVALUE_NOERROR) {
+ return ret;
+ }
+
// check if we can add this item
- ReturnValue ret = toCylinder->queryAdd(index, item, count, flags, actor);
+ 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);
@@ -2545,7 +2560,7 @@ ReturnValue Game::internalTeleport(const std::shared_ptr &thing, const Po
return RETURNVALUE_NOTPOSSIBLE;
}
-void Game::internalQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse) {
+void Game::playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse) {
if (!player || !corpse) {
return;
}
@@ -3292,6 +3307,11 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
Position walkToPos = fromPos;
ReturnValue ret = g_actions().canUse(player, fromPos);
if (ret == RETURNVALUE_NOERROR) {
@@ -3417,6 +3437,11 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) {
player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
return;
@@ -3524,6 +3549,11 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__)) {
if (std::shared_ptr houseTile = std::dynamic_pointer_cast(item->getTile())) {
const auto &house = houseTile->getHouse();
@@ -3663,6 +3693,11 @@ void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) {
}
}
+ if (parentContainer->hasOwner() && !parentContainer->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
if (parentContainer->hasPagination() && parentContainer->hasParent()) {
uint16_t indexContainer = std::floor(parentContainer->getThingIndex(container) / parentContainer->capacity()) * parentContainer->capacity();
player->addContainer(cid, parentContainer);
@@ -3706,6 +3741,16 @@ void Game::playerRotateItem(uint32_t playerId, const Position &pos, uint8_t stac
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
+ if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) {
+ player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
+ return;
+ }
+
if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
stdext::arraylist listDir(128);
if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
@@ -3746,6 +3791,16 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos,
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
+ if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) {
+ player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
+ return;
+ }
+
bool isPodiumOfRenown = itemId == ITEM_PODIUM_OF_RENOWN1 || itemId == ITEM_PODIUM_OF_RENOWN2;
if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
stdext::arraylist listDir(128);
@@ -3788,6 +3843,16 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
+ if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) {
+ player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
+ return;
+ }
+
const auto tile = item->getParent() ? item->getParent()->getTile() : nullptr;
if (!tile) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
@@ -3895,26 +3960,26 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st
return;
}
- const auto &house = map.houses.getHouseByPlayerId(player->getGUID());
- if (!house) {
- player->sendCancelMessage("You don't own a house, you need own a house to use this.");
- return;
- }
-
std::shared_ptr thing = internalGetThing(player, pos, stackPos, itemId, STACKPOS_FIND_THING);
if (!thing) {
return;
}
- std::shared_ptr
- item = thing->getItem();
- std::shared_ptr tile = map.getTile(item->getPosition());
- std::shared_ptr houseTile = std::dynamic_pointer_cast(tile);
+ const auto item = thing->getItem();
+ const auto tile = map.getTile(item->getPosition());
+ const auto houseTile = tile->dynamic_self_cast();
if (!tile->hasFlag(TILESTATE_PROTECTIONZONE) || !houseTile) {
player->sendCancelMessage("You may construct this only inside a house.");
return;
}
- if (houseTile->getHouse() != house) {
- player->sendCancelMessage("Only owners can wrap/unwrap inside a house.");
+ const auto house = houseTile->getHouse();
+ if (!house) {
+ player->sendCancelMessage("You may construct this only inside a house.");
+ return;
+ }
+
+ if (house->getHouseAccessLevel(player) < HOUSE_OWNER) {
+ player->sendCancelMessage("You are not allowed to construct this here.");
return;
}
@@ -3923,6 +3988,16 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
+ if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__) && !InternalGame::playerCanUseItemOnHouseTile(player, item)) {
+ player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT);
+ return;
+ }
+
if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
stdext::arraylist listDir(128);
if (player->getPathTo(pos, listDir, 0, 1, true, true)) {
@@ -3998,6 +4073,10 @@ std::shared_ptr
- Game::wrapItem(std::shared_ptr
- item, std::shared_ptr
}
void Game::unwrapItem(std::shared_ptr
- item, uint16_t unWrapId, std::shared_ptr house, std::shared_ptr player) {
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
auto hiddenCharges = item->getAttribute(DATE);
const ItemType &newiType = Item::items.getItemType(unWrapId);
if (player != nullptr && house != nullptr && newiType.isBed() && house->getMaxBeds() > -1 && house->getBedCount() >= house->getMaxBeds()) {
@@ -4041,6 +4120,11 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std::
return;
}
+ if (writeItem->hasOwner() && !writeItem->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
std::shared_ptr topParent = writeItem->getTopParent();
std::shared_ptr owner = std::dynamic_pointer_cast(topParent);
@@ -4161,6 +4245,11 @@ void Game::playerStowItem(uint32_t playerId, const Position &pos, uint16_t itemI
return;
}
+ if (item->hasOwner() && !item->isOwner(player)) {
+ player->sendCancelMessage(RETURNVALUE_ITEMISNOTYOURS);
+ return;
+ }
+
if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) {
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return;
@@ -4327,6 +4416,10 @@ void Game::playerRequestTrade(uint32_t playerId, const Position &pos, uint8_t st
player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE);
return;
}
+ if (tradeItem->isStoreItem() || tradeItem->hasOwner()) {
+ player->sendCancelMessage(RETURNVALUE_ITEMUNTRADEABLE);
+ return;
+ }
if (g_configManager().getBoolean(ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS, __FUNCTION__)) {
if (std::shared_ptr houseTile = std::dynamic_pointer_cast(tradeItem->getTile())) {
@@ -4433,6 +4526,10 @@ bool Game::internalStartTrade(std::shared_ptr player, std::shared_ptrsendCancelMessage(RETURNVALUE_THISPLAYERISALREADYTRADING);
return false;
}
+ if (tradeItem->isStoreItem() || tradeItem->hasOwner()) {
+ player->sendCancelMessage(RETURNVALUE_ITEMUNTRADEABLE);
+ return false;
+ }
player->tradePartner = tradePartner;
player->tradeItem = tradeItem;
@@ -5000,11 +5097,11 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item
auto rewardId = corpse->getAttribute(ItemAttribute_t::DATE);
auto reward = player->getReward(rewardId, false);
if (reward) {
- internalQuickLootCorpse(player, reward->getContainer());
+ playerQuickLootCorpse(player, reward->getContainer());
}
} else {
if (!lootAllCorpses) {
- internalQuickLootCorpse(player, corpse);
+ playerQuickLootCorpse(player, corpse);
} else {
playerLootAllCorpses(player, pos, lootAllCorpses);
}
@@ -5041,7 +5138,7 @@ void Game::playerLootAllCorpses(std::shared_ptr player, const Position &
}
corpses++;
- internalQuickLootCorpse(player, tileCorpse);
+ playerQuickLootCorpse(player, tileCorpse);
if (corpses >= 30) {
break;
}
@@ -5728,7 +5825,10 @@ bool Game::internalCreatureTurn(std::shared_ptr creature, Direction di
if (const auto &player = creature->getPlayer()) {
player->cancelPush();
}
- creature->setDirection(dir);
+
+ if (!creature->isDirectionLocked()) {
+ creature->setDirection(dir);
+ }
for (const auto &spectator : Spectators().find(creature->getPosition(), true)) {
spectator->getPlayer()->sendCreatureTurn(creature);
diff --git a/src/game/game.hpp b/src/game/game.hpp
index f446615e29a..9fb56f8b88c 100644
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -153,7 +153,7 @@ class Game {
std::shared_ptr getPlayerByID(uint32_t id, bool allowOffline = false);
- std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false);
+ std::shared_ptr getPlayerByName(const std::string &s, bool allowOffline = false, bool isNewName = false);
std::shared_ptr getPlayerByGUID(const uint32_t &guid, bool allowOffline = false);
@@ -350,6 +350,7 @@ class Game {
void playerSetFightModes(uint32_t playerId, FightMode_t fightMode, bool chaseMode, bool secureMode);
void playerLookAt(uint32_t playerId, uint16_t itemId, const Position &pos, uint8_t stackPos);
void playerLookInBattleList(uint32_t playerId, uint32_t creatureId);
+ void playerQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse);
void playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t itemId, uint8_t stackPos, std::shared_ptr
- defaultItem = nullptr, bool lootAllCorpses = false, bool autoLoot = false);
void playerLootAllCorpses(std::shared_ptr player, const Position &pos, bool lootAllCorpses);
void playerSetLootContainer(uint32_t playerId, ObjectCategory_t category, const Position &pos, uint16_t itemId, uint8_t stackPos);
@@ -697,13 +698,6 @@ class Game {
void playerSpeakToNpc(std::shared_ptr player, const std::string &text);
std::shared_ptr createPlayerTask(uint32_t delay, std::function f, std::string context) const;
- /**
- * Player wants to loot a corpse
- * \param player Player pointer
- * \param corpse Container pointer to be looted
- */
- void internalQuickLootCorpse(std::shared_ptr player, std::shared_ptr corpse);
-
/**
* @brief Finds the container for loot based on the given parameters.
*
diff --git a/src/game/movement/teleport.hpp b/src/game/movement/teleport.hpp
index ed2d752ad08..998e844b2d6 100644
--- a/src/game/movement/teleport.hpp
+++ b/src/game/movement/teleport.hpp
@@ -20,6 +20,10 @@ class Teleport final : public Item, public Cylinder {
return static_self_cast();
}
+ std::shared_ptr getCylinder() override final {
+ return getTeleport();
+ }
+
// serialization
Attr_ReadValue readAttr(AttrTypes_t attr, PropStream &propStream) override;
void serializeAttr(PropWriteStream &propWriteStream) const override;
diff --git a/src/io/iomapserialize.cpp b/src/io/iomapserialize.cpp
index db1bac1e563..7f351d2b617 100644
--- a/src/io/iomapserialize.cpp
+++ b/src/io/iomapserialize.cpp
@@ -46,11 +46,21 @@ void IOMapSerialize::loadHouseItems(Map* map) {
}
while (item_count--) {
+ if (auto houseTile = std::dynamic_pointer_cast(tile)) {
+ const auto house = houseTile->getHouse();
+ if (house->getOwner() == 0) {
+ g_logger().trace("Skipping load item from house id: {}, position: {}, house does not have owner", house->getId(), house->getEntryPosition().toString());
+ house->clearHouseInfo(false);
+ continue;
+ }
+ }
+
loadItem(propStream, tile, true);
}
} while (result->next());
g_logger().info("Loaded house items in {} milliseconds", bm_context.duration());
}
+
bool IOMapSerialize::saveHouseItems() {
bool success = DBTransaction::executeWithinTransaction([]() {
return SaveHouseItemsGuard();
@@ -269,7 +279,7 @@ bool IOMapSerialize::loadHouseInfo() {
do {
auto houseId = result->getNumber("id");
- const auto &house = g_game().map.houses.getHouse(houseId);
+ const auto house = g_game().map.houses.getHouse(houseId);
if (house) {
uint32_t owner = result->getNumber("owner");
int32_t newOwner = result->getNumber("new_owner");
diff --git a/src/items/containers/container.cpp b/src/items/containers/container.cpp
index cfc77c4416d..f5ad4cfab60 100644
--- a/src/items/containers/container.cpp
+++ b/src/items/containers/container.cpp
@@ -450,6 +450,18 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr &
if (item == getItem()) {
return RETURNVALUE_THISISIMPOSSIBLE;
}
+ if (item->hasOwner()) {
+ // a non-owner can move the item around but not pick it up
+ auto toPlayer = getTopParent()->getPlayer();
+ if (toPlayer && !item->isOwner(toPlayer)) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
+
+ // a container cannot have items of different owners
+ if (hasOwner() && getOwnerId() != item->getOwnerId()) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
+ }
std::shared_ptr cylinder = getParent();
auto noLimit = hasBitSet(FLAG_NOLIMIT, flags);
@@ -473,8 +485,8 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr &
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
- if (std::shared_ptr topParentContainer = getTopParentContainer()) {
- if (std::shared_ptr addContainer = item->getContainer()) {
+ if (const auto topParentContainer = getTopParentContainer()) {
+ if (const auto addContainer = item->getContainer()) {
uint32_t addContainerCount = addContainer->getContainerHoldingCount() + 1;
uint32_t maxContainer = static_cast(g_configManager().getNumber(MAX_CONTAINER, __FUNCTION__));
if (addContainerCount + topParentContainer->getContainerHoldingCount() > maxContainer) {
@@ -485,10 +497,10 @@ ReturnValue Container::queryAdd(int32_t addIndex, const std::shared_ptr &
if (addItemCount + topParentContainer->getItemHoldingCount() > m_maxItems) {
return RETURNVALUE_CONTAINERISFULL;
}
- }
-
- if (topParentContainer->getItemHoldingCount() + 1 > m_maxItems) {
- return RETURNVALUE_CONTAINERISFULL;
+ } else {
+ if (topParentContainer->getItemHoldingCount() + 1 > m_maxItems) {
+ return RETURNVALUE_CONTAINERISFULL;
+ }
}
}
@@ -950,3 +962,17 @@ void ContainerIterator::advance() {
}
}
}
+
+uint32_t Container::getOwnerId() const {
+ uint32_t ownerId = Item::getOwnerId();
+ if (ownerId > 0) {
+ return ownerId;
+ }
+ for (const auto &item : itemlist) {
+ ownerId = item->getOwnerId();
+ if (ownerId > 0) {
+ return ownerId;
+ }
+ }
+ return 0;
+}
diff --git a/src/items/containers/container.hpp b/src/items/containers/container.hpp
index 70d4571bafd..0f3f1df4398 100644
--- a/src/items/containers/container.hpp
+++ b/src/items/containers/container.hpp
@@ -59,6 +59,10 @@ class Container : public Item, public Cylinder {
return static_self_cast();
}
+ std::shared_ptr getCylinder() override final {
+ return getContainer();
+ }
+
std::shared_ptr getRootContainer();
virtual std::shared_ptr getDepotLocker() {
@@ -165,6 +169,8 @@ class Container : public Item, public Cylinder {
virtual void removeItem(std::shared_ptr thing, bool sendUpdateToClient = false);
+ uint32_t getOwnerId() const override final;
+
bool isAnyKindOfRewardChest();
bool isAnyKindOfRewardContainer();
bool isBrowseFieldAndHoldsRewardChest();
diff --git a/src/items/containers/depot/depotchest.cpp b/src/items/containers/depot/depotchest.cpp
index 01191579840..198ec3e14f0 100644
--- a/src/items/containers/depot/depotchest.cpp
+++ b/src/items/containers/depot/depotchest.cpp
@@ -24,6 +24,9 @@ ReturnValue DepotChest::queryAdd(int32_t index, const std::shared_ptr &th
if (item == nullptr) {
return RETURNVALUE_NOTPOSSIBLE;
}
+ if (actor && item->hasOwner() && !item->isOwner(actor)) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
bool skipLimit = hasBitSet(FLAG_NOLIMIT, flags);
if (!skipLimit) {
diff --git a/src/items/containers/mailbox/mailbox.cpp b/src/items/containers/mailbox/mailbox.cpp
index 9253bdd7417..19b3db5367a 100644
--- a/src/items/containers/mailbox/mailbox.cpp
+++ b/src/items/containers/mailbox/mailbox.cpp
@@ -138,5 +138,5 @@ bool Mailbox::getReceiver(std::shared_ptr
- item, std::string &name) const {
}
bool Mailbox::canSend(std::shared_ptr
- item) {
- return item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER;
+ return !item->hasOwner() && (item->getID() == ITEM_PARCEL || item->getID() == ITEM_LETTER);
}
diff --git a/src/items/containers/mailbox/mailbox.hpp b/src/items/containers/mailbox/mailbox.hpp
index 3d57c72b5ef..5f47b61b290 100644
--- a/src/items/containers/mailbox/mailbox.hpp
+++ b/src/items/containers/mailbox/mailbox.hpp
@@ -21,6 +21,10 @@ class Mailbox final : public Item, public Cylinder {
return static_self_cast();
}
+ std::shared_ptr getCylinder() override final {
+ return getMailbox();
+ }
+
// cylinder implementations
ReturnValue queryAdd(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t flags, std::shared_ptr actor = nullptr) override;
ReturnValue queryMaxCount(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t &maxQueryCount, uint32_t flags) override;
diff --git a/src/items/functions/item/item_parse.cpp b/src/items/functions/item/item_parse.cpp
index 8d29dfb71a9..415aa5acb99 100644
--- a/src/items/functions/item/item_parse.cpp
+++ b/src/items/functions/item/item_parse.cpp
@@ -73,6 +73,7 @@ void ItemParse::initParse(const std::string &tmpStrValue, pugi::xml_node attribu
ItemParse::parseReflectDamage(tmpStrValue, valueAttribute, itemType);
ItemParse::parseTransformOnUse(tmpStrValue, valueAttribute, itemType);
ItemParse::parsePrimaryType(tmpStrValue, valueAttribute, itemType);
+ ItemParse::parseHouseRelated(tmpStrValue, valueAttribute, itemType);
}
void ItemParse::parseDummyRate(pugi::xml_node attributeNode, ItemType &itemType) {
@@ -572,6 +573,8 @@ void ItemParse::parseAbsorbPercent(const std::string &tmpStrValue, pugi::xml_att
itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value());
} else if (stringValue == "absorbpercentpoison" || stringValue == "absorbpercentearth") {
itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value());
+ } else if (stringValue == "absorbpercentearth") {
+ itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value());
} else if (stringValue == "absorbpercentice") {
itemType.getAbilities().absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value());
} else if (stringValue == "absorbpercentholy") {
@@ -954,3 +957,10 @@ void ItemParse::parsePrimaryType(const std::string_view &tmpStrValue, pugi::xml_
itemType.m_primaryType = asLowerCaseString(valueAttribute.as_string());
}
}
+
+void ItemParse::parseHouseRelated(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType) {
+ if (tmpStrValue == "usedbyhouseguests") {
+ g_logger().debug("[{}] item {}, used by guests {}", __FUNCTION__, itemType.id, valueAttribute.as_bool());
+ itemType.m_canBeUsedByGuests = valueAttribute.as_bool();
+ }
+}
diff --git a/src/items/functions/item/item_parse.hpp b/src/items/functions/item/item_parse.hpp
index f29fc79971a..2ebffc94266 100644
--- a/src/items/functions/item/item_parse.hpp
+++ b/src/items/functions/item/item_parse.hpp
@@ -156,6 +156,7 @@ const phmap::flat_hash_map ItemParseAttribut
{ "reflectdamage", ITEM_PARSE_REFLECTDAMAGE },
{ "reflectpercentall", ITEM_PARSE_REFLECTPERCENTALL },
{ "primarytype", ITEM_PARSE_PRIMARYTYPE },
+ { "usedbyhouseguests", ITEM_PARSE_USEDBYGUESTS },
};
const phmap::flat_hash_map ItemTypesMap = {
@@ -310,6 +311,7 @@ class ItemParse : public Items {
static void parseReflectDamage(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType);
static void parseTransformOnUse(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType);
static void parsePrimaryType(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType);
+ static void parseHouseRelated(const std::string_view &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType);
private:
// Parent of the function: static void parseField
diff --git a/src/items/item.cpp b/src/items/item.cpp
index 1632cba95dc..a3f0b3f1a4b 100644
--- a/src/items/item.cpp
+++ b/src/items/item.cpp
@@ -238,7 +238,15 @@ bool Item::equals(std::shared_ptr
- compareItem) const {
return false;
}
+ if (getOwnerId() != compareItem->getOwnerId()) {
+ return false;
+ }
+
for (const auto &attribute : getAttributeVector()) {
+ if (attribute.getAttributeType() == ItemAttribute_t::STORE) {
+ continue;
+ }
+
for (const auto &compareAttribute : compareItem->getAttributeVector()) {
if (attribute.getAttributeType() != compareAttribute.getAttributeType()) {
continue;
@@ -305,6 +313,20 @@ void Item::setID(uint16_t newid) {
}
}
+bool Item::isOwner(uint32_t ownerId) {
+ if (getOwnerId() == ownerId) {
+ return true;
+ }
+ if (ownerId >= Player::getFirstID() && ownerId <= Player::getLastID()) {
+ const auto &player = g_game().getPlayerByID(ownerId);
+ return player && player->getGUID() == getOwnerId();
+ }
+ if (auto player = g_game().getPlayerByGUID(ownerId); player) {
+ return player->getID() == getOwnerId();
+ }
+ return false;
+}
+
std::shared_ptr Item::getTopParent() {
std::shared_ptr aux = getParent();
std::shared_ptr prevaux = std::dynamic_pointer_cast(shared_from_this());
@@ -357,6 +379,9 @@ std::shared_ptr Item::getHoldingPlayer() {
}
bool Item::isItemStorable() const {
+ if (isStoreItem() || hasOwner()) {
+ return false;
+ }
auto isContainerAndHasSomethingInside = (getContainer() != NULL) && (getContainer()->getItemList().size() > 0);
return (isStowable() || isContainerAndHasSomethingInside);
}
@@ -775,6 +800,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream &propStream) {
break;
}
+ case ATTR_OWNER: {
+ uint32_t ownerId;
+ if (!propStream.read(ownerId)) {
+ g_logger().error("[{}] failed to read amount", __FUNCTION__);
+ return ATTR_READ_ERROR;
+ }
+
+ setAttribute(OWNER, ownerId);
+ break;
+ }
default:
return ATTR_READ_ERROR;
}
@@ -935,11 +970,17 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const {
propWriteStream.write(ATTR_AMOUNT);
propWriteStream.write(getAttribute(AMOUNT));
}
+
if (hasAttribute(STORE_INBOX_CATEGORY)) {
propWriteStream.write(ATTR_STORE_INBOX_CATEGORY);
propWriteStream.writeString(getString(ItemAttribute_t::STORE_INBOX_CATEGORY));
}
+ if (hasAttribute(OWNER)) {
+ propWriteStream.write(ATTR_OWNER);
+ propWriteStream.write(getAttribute(ItemAttribute_t::OWNER));
+ }
+
// Serialize custom attributes, only serialize if the map not is empty
if (hasCustomAttribute()) {
auto customAttributeMap = getCustomAttributeMap();
@@ -954,6 +995,50 @@ void Item::serializeAttr(PropWriteStream &propWriteStream) const {
}
}
+void Item::setOwner(std::shared_ptr owner) {
+ auto id = owner->getID();
+ if (owner->getPlayer()) {
+ id = owner->getPlayer()->getGUID();
+ }
+ setOwner(id);
+}
+
+bool Item::isOwner(std::shared_ptr owner) {
+ if (!owner) {
+ return false;
+ }
+ auto id = owner->getID();
+ if (isOwner(id)) {
+ return true;
+ }
+ if (owner->getPlayer()) {
+ id = owner->getPlayer()->getGUID();
+ }
+ return isOwner(id);
+}
+
+uint32_t Item::getOwnerId() const {
+ if (hasAttribute(ItemAttribute_t::OWNER)) {
+ return getAttribute(ItemAttribute_t::OWNER);
+ }
+ return 0;
+}
+
+std::string Item::getOwnerName() {
+ if (!hasOwner()) {
+ return "";
+ }
+
+ auto creature = g_game().getCreatureByID(getOwnerId());
+ if (creature) {
+ return creature->getName();
+ }
+ if (auto name = g_game().getPlayerNameByGUID(getOwnerId()); !name.empty()) {
+ return name;
+ }
+ return "someone else";
+}
+
bool Item::hasProperty(ItemProperty prop) const {
const ItemType &it = items[id];
switch (prop) {
@@ -987,7 +1072,16 @@ bool Item::hasProperty(ItemProperty prop) const {
}
bool Item::canBeMoved() const {
- return isMoveable() && !hasAttribute(UNIQUEID) && (!hasAttribute(ACTIONID) || getAttribute(ItemAttribute_t::ACTIONID) != IMMOVABLE_ACTION_ID);
+ static std::unordered_set immovableActionIds = {
+ IMMOVABLE_ACTION_ID,
+ };
+ if (hasAttribute(ItemAttribute_t::UNIQUEID)) {
+ return false;
+ }
+ if (hasAttribute(ItemAttribute_t::ACTIONID) && immovableActionIds.contains(static_cast(getAttribute(ItemAttribute_t::ACTIONID)))) {
+ return false;
+ }
+ return isMoveable();
}
void Item::checkDecayMapItemOnMove() {
@@ -3142,11 +3236,7 @@ bool Item::hasMarketAttributes() const {
}
}
- if (hasImbuements()) {
- return false;
- }
-
- return true;
+ return !hasImbuements() && !isStoreItem() && !hasOwner();
}
bool Item::isInsideDepot(bool includeInbox /* = false*/) {
diff --git a/src/items/item.hpp b/src/items/item.hpp
index 5dce067504f..956adad61cf 100644
--- a/src/items/item.hpp
+++ b/src/items/item.hpp
@@ -269,6 +269,28 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject {
return isLootTrackeable;
}
+ void setOwner(uint32_t owner) {
+ setAttribute(ItemAttribute_t::OWNER, owner);
+ }
+
+ void setOwner(std::shared_ptr owner);
+
+ virtual uint32_t getOwnerId() const;
+
+ bool isOwner(uint32_t ownerId);
+
+ std::string getOwnerName();
+
+ bool isOwner(std::shared_ptr owner);
+
+ bool hasOwner() const {
+ return getOwnerId() != 0;
+ }
+
+ bool canBeMovedToStore() const {
+ return isStoreItem() || hasOwner();
+ }
+
static std::string parseImbuementDescription(std::shared_ptr
- item);
static std::string parseShowDurationSpeed(int32_t speed, bool &begin);
static std::string parseShowDuration(std::shared_ptr
- item);
@@ -473,6 +495,9 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject {
bool canReceiveAutoCarpet() const {
return isBlocking() && isAlwaysOnTop() && !items[id].hasHeight;
}
+ bool canBeUsedByGuests() const {
+ return isDummy() || items[id].m_canBeUsedByGuests;
+ }
bool isDecayDisabled() const {
return decayDisabled;
diff --git a/src/items/items.hpp b/src/items/items.hpp
index 16971801960..5320ec86ab5 100644
--- a/src/items/items.hpp
+++ b/src/items/items.hpp
@@ -342,6 +342,7 @@ class ItemType {
bool loaded = false;
bool spellbook = false;
bool isWrapKit = false;
+ bool m_canBeUsedByGuests = false;
};
class Items {
diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp
index c6326acd28b..4e8fffd3a37 100644
--- a/src/items/items_definitions.hpp
+++ b/src/items/items_definitions.hpp
@@ -119,6 +119,8 @@ enum ReturnValue {
RETURNVALUE_REWARDCHESTISEMPTY,
RETURNVALUE_REWARDCONTAINERISEMPTY,
RETURNVALUE_CONTACTADMINISTRATOR,
+ RETURNVALUE_ITEMISNOTYOURS,
+ RETURNVALUE_ITEMUNTRADEABLE,
};
enum ItemGroup_t {
@@ -236,6 +238,7 @@ enum AttrTypes_t {
ATTR_TIER = 40,
ATTR_CUSTOM = 41,
ATTR_STORE_INBOX_CATEGORY = 42,
+ ATTR_OWNER = 43,
// Always the last
ATTR_NONE = 0
@@ -597,6 +600,7 @@ enum ItemParseAttributes_t {
ITEM_PARSE_REFLECTPERCENTALL,
ITEM_PARSE_REFLECTDAMAGE,
ITEM_PARSE_PRIMARYTYPE,
+ ITEM_PARSE_USEDBYGUESTS,
};
struct ImbuementInfo {
diff --git a/src/items/thing.hpp b/src/items/thing.hpp
index 05f961ec9a1..bc7d7f3af78 100644
--- a/src/items/thing.hpp
+++ b/src/items/thing.hpp
@@ -69,6 +69,9 @@ class Thing {
virtual std::shared_ptr getCreature() const {
return nullptr;
}
+ virtual std::shared_ptr getCylinder() {
+ return nullptr;
+ }
virtual bool isRemoved() {
return true;
diff --git a/src/items/tile.cpp b/src/items/tile.cpp
index 2b9527095dd..cc9d1160211 100644
--- a/src/items/tile.cpp
+++ b/src/items/tile.cpp
@@ -65,6 +65,10 @@ bool Tile::hasProperty(std::shared_ptr
- exclude, ItemProperty prop) const {
if (const TileItemVector* items = getItemList()) {
for (auto &item : *items) {
+ if (!item) {
+ g_logger().error("Tile::hasProperty: tile {} has an item which is nullptr", tilePos.toString());
+ continue;
+ }
if (item != exclude && item->hasProperty(prop)) {
return true;
}
@@ -857,7 +861,7 @@ ReturnValue Tile::queryRemove(const std::shared_ptr &thing, uint32_t coun
return RETURNVALUE_NOERROR;
}
-std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_ptr &, std::shared_ptr
- * destItem, uint32_t &tileFlags) {
+std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_ptr &thing, std::shared_ptr
- * destItem, uint32_t &tileFlags) {
std::shared_ptr destTile = nullptr;
*destItem = nullptr;
@@ -948,6 +952,12 @@ std::shared_ptr Tile::queryDestination(int32_t &, const std::shared_pt
std::shared_ptr destThing = destTile->getTopDownItem();
if (destThing) {
*destItem = destThing->getItem();
+ if (thing->getItem()) {
+ auto destCylinder = destThing->getCylinder();
+ if (destCylinder && !destCylinder->getContainer()) {
+ return destThing->getCylinder();
+ }
+ }
}
}
return destTile;
@@ -1563,7 +1573,7 @@ void Tile::internalAddThing(uint32_t, std::shared_ptr thing) {
zone->thingAdded(thing);
}
- thing->setParent(static_self_cast());
+ thing->setParent(getTile());
std::shared_ptr creature = thing->getCreature();
if (creature) {
diff --git a/src/items/tile.hpp b/src/items/tile.hpp
index bb10b309147..2493974c950 100644
--- a/src/items/tile.hpp
+++ b/src/items/tile.hpp
@@ -131,6 +131,11 @@ class Tile : public Cylinder, public SharedObject {
std::shared_ptr getTile() override final {
return static_self_cast();
}
+
+ std::shared_ptr getCylinder() override final {
+ return getTile();
+ }
+
std::shared_ptr getFieldItem() const;
std::shared_ptr getTeleportItem() const;
std::shared_ptr getTrashHolder() const;
diff --git a/src/items/trashholder.cpp b/src/items/trashholder.cpp
index 024bb31fcd0..835558d6244 100644
--- a/src/items/trashholder.cpp
+++ b/src/items/trashholder.cpp
@@ -12,7 +12,14 @@
#include "items/trashholder.hpp"
#include "game/game.hpp"
-ReturnValue TrashHolder::queryAdd(int32_t, const std::shared_ptr &, uint32_t, uint32_t, std::shared_ptr) {
+ReturnValue TrashHolder::queryAdd(int32_t, const std::shared_ptr &thing, uint32_t, uint32_t, std::shared_ptr actor) {
+ std::shared_ptr
- item = thing->getItem();
+ if (item == nullptr) {
+ return RETURNVALUE_NOERROR;
+ }
+ if (item->hasOwner() && !item->isOwner(actor)) {
+ return RETURNVALUE_ITEMISNOTYOURS;
+ }
return RETURNVALUE_NOERROR;
}
diff --git a/src/items/trashholder.hpp b/src/items/trashholder.hpp
index 5043e897d1e..0aa52ed7bee 100644
--- a/src/items/trashholder.hpp
+++ b/src/items/trashholder.hpp
@@ -21,6 +21,10 @@ class TrashHolder final : public Item, public Cylinder {
return static_self_cast();
}
+ std::shared_ptr getCylinder() override final {
+ return getTrashHolder();
+ }
+
// cylinder implementations
ReturnValue queryAdd(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t flags, std::shared_ptr actor = nullptr) override;
ReturnValue queryMaxCount(int32_t index, const std::shared_ptr &thing, uint32_t count, uint32_t &maxQueryCount, uint32_t flags) override;
diff --git a/src/kv/kv.hpp b/src/kv/kv.hpp
index 99bc1f695a6..ea76276c80c 100644
--- a/src/kv/kv.hpp
+++ b/src/kv/kv.hpp
@@ -43,7 +43,7 @@ class KV : public std::enable_shared_from_this {
class KVStore : public KV {
public:
- static constexpr size_t MAX_SIZE = 10000;
+ static constexpr size_t MAX_SIZE = 1000000;
static KVStore &getInstance();
explicit KVStore(Logger &logger) :
diff --git a/src/lua/creature/raids.cpp b/src/lua/creature/raids.cpp
index bb0d1f1af43..3251f744e4f 100644
--- a/src/lua/creature/raids.cpp
+++ b/src/lua/creature/raids.cpp
@@ -21,7 +21,7 @@ Raids::Raids() {
}
bool Raids::loadFromXml() {
- if (isLoaded()) {
+ if (g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__) || isLoaded()) {
return true;
}
@@ -96,7 +96,7 @@ bool Raids::loadFromXml() {
static constexpr int32_t MAX_RAND_RANGE = 10000000;
bool Raids::startup() {
- if (!isLoaded() || isStarted()) {
+ if (!isLoaded() || isStarted() || g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__)) {
return false;
}
@@ -109,6 +109,9 @@ bool Raids::startup() {
}
void Raids::checkRaids() {
+ if (g_configManager().getBoolean(DISABLE_LEGACY_RAIDS, __FUNCTION__)) {
+ return;
+ }
if (!getRunning()) {
uint64_t now = OTSYS_TIME();
diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp
index 633f39489de..5221d2c9e4f 100644
--- a/src/lua/functions/core/game/game_functions.cpp
+++ b/src/lua/functions/core/game/game_functions.cpp
@@ -635,9 +635,10 @@ int GameFunctions::luaGameGetOfflinePlayer(lua_State* L) {
}
int GameFunctions::luaGameGetNormalizedPlayerName(lua_State* L) {
- // Game.getNormalizedPlayerName(name)
+ // Game.getNormalizedPlayerName(name[, isNewName = false])
auto name = getString(L, 1);
- std::shared_ptr player = g_game().getPlayerByName(name, true);
+ auto isNewName = getBoolean(L, 2, false);
+ std::shared_ptr player = g_game().getPlayerByName(name, true, isNewName);
if (player) {
pushString(L, player->getName());
} else {
diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp
index 5d566920c7b..306283b0b6e 100644
--- a/src/lua/functions/core/game/lua_enums.cpp
+++ b/src/lua/functions/core/game/lua_enums.cpp
@@ -1200,6 +1200,8 @@ void LuaEnums::initReturnValueEnums(lua_State* L) {
registerEnum(L, RETURNVALUE_NOTENOUGHFISHLEVEL);
registerEnum(L, RETURNVALUE_REWARDCHESTISEMPTY);
registerEnum(L, RETURNVALUE_CONTACTADMINISTRATOR);
+ registerEnum(L, RETURNVALUE_ITEMISNOTYOURS);
+ registerEnum(L, RETURNVALUE_ITEMUNTRADEABLE);
}
// Reload
diff --git a/src/lua/functions/core/network/network_message_functions.cpp b/src/lua/functions/core/network/network_message_functions.cpp
index fc1c1688613..630cd185d1b 100644
--- a/src/lua/functions/core/network/network_message_functions.cpp
+++ b/src/lua/functions/core/network/network_message_functions.cpp
@@ -232,7 +232,7 @@ int NetworkMessageFunctions::luaNetworkMessageAddDouble(lua_State* L) {
int NetworkMessageFunctions::luaNetworkMessageAddItem(lua_State* L) {
// networkMessage:addItem(item, player)
- const auto &item = getUserdataShared
- (L, 2);
+ std::shared_ptr
- item = getUserdataShared
- (L, 2);
if (!item) {
reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND));
lua_pushnil(L);
@@ -289,7 +289,7 @@ int NetworkMessageFunctions::luaNetworkMessageSendToPlayer(lua_State* L) {
return 1;
}
- const auto &player = getPlayer(L, 2);
+ std::shared_ptr player = getPlayer(L, 2);
if (!player) {
reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND));
return 1;
diff --git a/src/lua/functions/creatures/combat/combat_functions.cpp b/src/lua/functions/creatures/combat/combat_functions.cpp
index 6c1529726bf..3661536aea6 100644
--- a/src/lua/functions/creatures/combat/combat_functions.cpp
+++ b/src/lua/functions/creatures/combat/combat_functions.cpp
@@ -88,7 +88,7 @@ int CombatFunctions::luaCombatSetArea(lua_State* L) {
int CombatFunctions::luaCombatSetCondition(lua_State* L) {
// combat:addCondition(condition)
std::shared_ptr condition = getUserdataShared(L, 2);
- const auto &combat = getUserdataShared(L, 1);
+ Combat* combat = getUserdata(L, 1);
if (combat && condition) {
combat->addCondition(condition->clone());
pushBoolean(L, true);
diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp
index 5965adb7f8f..8315a690f36 100644
--- a/src/lua/functions/creatures/creature_functions.cpp
+++ b/src/lua/functions/creatures/creature_functions.cpp
@@ -581,6 +581,29 @@ int CreatureFunctions::luaCreatureSetMoveLocked(lua_State* L) {
return 1;
}
+int CreatureFunctions::luaCreatureIsDirectionLocked(lua_State* L) {
+ // creature:isDirectionLocked()
+ std::shared_ptr creature = getUserdataShared(L, 1);
+ if (creature) {
+ pushBoolean(L, creature->isDirectionLocked());
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int CreatureFunctions::luaCreatureSetDirectionLocked(lua_State* L) {
+ // creature:setDirectionLocked(directionLocked)
+ std::shared_ptr creature = getUserdataShared(L, 1);
+ if (creature) {
+ creature->setDirectionLocked(getBoolean(L, 2));
+ pushBoolean(L, true);
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
int CreatureFunctions::luaCreatureGetSkull(lua_State* L) {
// creature:getSkull()
std::shared_ptr creature = getUserdataShared(L, 1);
@@ -682,7 +705,11 @@ int CreatureFunctions::luaCreatureRemoveCondition(lua_State* L) {
const std::shared_ptr condition = creature->getCondition(conditionType, conditionId, subId);
if (condition) {
bool force = getBoolean(L, 5, false);
- creature->removeCondition(conditionType, conditionId, force);
+ if (subId == 0) {
+ creature->removeCondition(conditionType, conditionId, force);
+ } else {
+ creature->removeCondition(condition);
+ }
pushBoolean(L, true);
} else {
lua_pushnil(L);
diff --git a/src/lua/functions/creatures/creature_functions.hpp b/src/lua/functions/creatures/creature_functions.hpp
index af86c5c7097..48c374936c0 100644
--- a/src/lua/functions/creatures/creature_functions.hpp
+++ b/src/lua/functions/creatures/creature_functions.hpp
@@ -60,7 +60,9 @@ class CreatureFunctions final : LuaScriptInterface {
registerMethod(L, "Creature", "setMaxHealth", CreatureFunctions::luaCreatureSetMaxHealth);
registerMethod(L, "Creature", "setHiddenHealth", CreatureFunctions::luaCreatureSetHiddenHealth);
registerMethod(L, "Creature", "isMoveLocked", CreatureFunctions::luaCreatureIsMoveLocked);
+ registerMethod(L, "Creature", "isDirectionLocked", CreatureFunctions::luaCreatureIsDirectionLocked);
registerMethod(L, "Creature", "setMoveLocked", CreatureFunctions::luaCreatureSetMoveLocked);
+ registerMethod(L, "Creature", "setDirectionLocked", CreatureFunctions::luaCreatureSetDirectionLocked);
registerMethod(L, "Creature", "getSkull", CreatureFunctions::luaCreatureGetSkull);
registerMethod(L, "Creature", "setSkull", CreatureFunctions::luaCreatureSetSkull);
registerMethod(L, "Creature", "getOutfit", CreatureFunctions::luaCreatureGetOutfit);
@@ -151,6 +153,9 @@ class CreatureFunctions final : LuaScriptInterface {
static int luaCreatureIsMoveLocked(lua_State* L);
static int luaCreatureSetMoveLocked(lua_State* L);
+ static int luaCreatureIsDirectionLocked(lua_State* L);
+ static int luaCreatureSetDirectionLocked(lua_State* L);
+
static int luaCreatureGetSkull(lua_State* L);
static int luaCreatureSetSkull(lua_State* L);
diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp
index 5e32115a21c..0c6bcf8c5bf 100644
--- a/src/lua/functions/creatures/monster/monster_functions.cpp
+++ b/src/lua/functions/creatures/monster/monster_functions.cpp
@@ -589,7 +589,24 @@ int MonsterFunctions::luaMonsterHazardDamageBoost(lua_State* L) {
pushBoolean(L, monster->getHazardSystemDamageBoost());
} else {
monster->setHazardSystemDamageBoost(hazardDamageBoost);
- pushBoolean(L, monster->getHazardSystemCrit());
+ pushBoolean(L, monster->getHazardSystemDamageBoost());
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int MonsterFunctions::luaMonsterHazardDefenseBoost(lua_State* L) {
+ // get: monster:hazardDefenseBoost() ; set: monster:hazardDefenseBoost(hazardDefenseBoost)
+ std::shared_ptr